Integrate Combine into legacy frameworks

Integrate Combine into legacy frameworks

2019/08/05 Combineゴリゴリキャッチアップ会
Twitter hashtag: #combine_meetup

439ebe4787a98881df8a59d41baf4a43?s=128

Ryo Aoyama

August 05, 2019
Tweet

Transcript

  1. Integrate Combine into legacy frameworks 2019.08.05 MON 
 CombineΰϦΰϦΩϟονΞοϓձ #combine_meetup

    Ryo Aoyama GitHub: @ra1028 Twitter: @ra1028fe5
  2. PROFILE Ryo Aoyama Cyberagent, Inc Ὂ CATS iOS Lead Ὂ

    WinTicket OSS Author Ὂ DifferenceKit, Carbon, VueFlux, etc… GitHub: @ra1028 Twitter: @ra1028fe5
  3. Combine has released

  4. Current Status: iOS 13 beta 5 Xcode 11 beta 5

  5. ࠓ೔ͷ࿩୊

  6. Integrate Combine into legacy frameworks

  7. Combine ✕ Foundation

  8. Publisher KeyValueObserving URLSession NotificationCenter Timer Combine ✕ Foundation TopLevelEncoder /Decoder

    JSONEncoder JSONDecoder PropertyListEncoder PropertyListDecoder Scheduler RunLoop OperationQueue DispatchQueue
  9. Publisher KeyValueObserving URLSession NotificationCenter Timer Combine ✕ Foundation TopLevelEncoder /Decoder

    JSONEncoder JSONDecoder PropertyListEncoder PropertyListDecoder Scheduler RunLoop OperationQueue DispatchQueue
  10. Publisher KeyValueObserving URLSession NotificationCenter Timer Combine ✕ Foundation TopLevelEncoder /Decoder

    JSONEncoder JSONDecoder PropertyListEncoder PropertyListDecoder Scheduler RunLoop OperationQueue DispatchQueue
  11. Publisher KeyValueObserving URLSession NotificationCenter Timer Combine ✕ Foundation TopLevelEncoder /Decoder

    JSONEncoder JSONDecoder PropertyListEncoder PropertyListDecoder Scheduler RunLoop OperationQueue DispatchQueue
  12. Combine ✕ UIKit

  13. N/A

  14. طଘFrameworkͱCombineΛ౷߹͍ͯ͘͠ʹ͸ʁ

  15. Publisher Declares that a type can transmit a sequence of

    values over time. protocol Publisher { associatedtype Output associatedtype Failure: Error func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input }
  16. Publisher URLSession extension URLSession { struct DataTaskPublisher: Publisher { typealias

    Output = (data: Data, response: URLResponse) typealias Failure = URLError init(request: URLRequest, session: URLSession) } func dataTaskPublisher(for url: URL) -> DataTaskPublisher }
  17. ֤Publisher͸ͦΕͧΕͷৼΔ෣͍ʹରͯ͠ ઐ༻ͷܕΛఆ͍ٛͯ͠Δ

  18. RxSwift.Observable.create͸?

  19. Ωϟϯηϧ͠ͳ͍ඇಉظॲཧͳΒFuture͕ར༻Ͱ͖Δ

  20. extension UNUserNotificationCenter { func requestAuthorizationFuture(options: UNAuthorizationOptions) -> Future<Bool, Error> {

    Future { promise in self.requestAuthorization(options: options) { hasAuthorized, error in if let error = error { return promise(.failure(error)) } promise(.success(hasAuthorized)) } } } }
  21. ͨͩ͠ΩϟϯηϧෆՄ Hotͳੑ࣭Λ΋ͪɺFutureΛΠϯελϯεԽ ͨ࣌͠఺Ͱ࣮ߦ͞ΕΔ͜ͱʹ஫ҙ

  22. AnyPublisher.init AnyPublisher<Int, Never> { subscriber in _ = subscriber.receive(value) subscriber.receive(completion:

    .finished) } Xcode Beta 3
  23. AnyPublisher.init AnyPublisher<Int, Never> { subscriber in _ = subscriber.receive(value) subscriber.receive(completion:

    .finished) } Xcode Beta 4+
  24. ObservableͷΑ͏ͳ൚༻ܕΛଟ໨తʹ࢖͏Α͏ͳ API͸ఏڙ͞Ε͍ͯͳ͍ ͋͘·ͰطଘͷPublisherͷcomposition͔ ઐ༻ͷܕΛఆٛ͢Δ͜ͱΛਪ঑͍ͯ͠ΔΑ͏

  25. The Pattern of Combine Source: https://developer.apple.com/videos/play/wwdc2019/722/

  26. Publisher Subscription Subscriber

  27. ྫͱͯ͠ CADisplayLink + Publisher Λ࣮૷ͯ͠ΈΔ

  28. CADisplayLink A timer object that allows your application to synchronize

    its drawing to the refresh rate of the display. class CADisplayLink: NSObject { var preferredFramesPerSecond: Int init(target: Any, selector sel: Selector) func add(to runloop: RunLoop, forMode mode: RunLoop.Mode) func invalidate() }
  29. None
  30. extension CADisplayLink { struct Publisher: Combine.Publisher { typealias Output =

    CADisplayLink typealias Failure = Never var runLoop: RunLoop var mode: RunLoop.Mode var preferredFramesPerSecond: Int func receive<S: Subscriber>(subscriber: S) where Failure == S.Failure, Output == S.Input { #warning("TODO:") } } }
  31. None
  32. ※ initলུ extension CADisplayLink { final class Subscription<S: Subscriber>: Combine.Subscription

    where S.Input == CADisplayLink { let combineIdentifier = CombineIdentifier() let subscriber: S let runLoop: RunLoop let mode: RunLoop.Mode let preferredFramesPerSecond: Int private var displayLink: CADisplayLink? @objc func receive(sender: CADisplayLink) { _ = subscriber.receive(sender) } func request(_ demand: Subscribers.Demand) { let displayLink = CADisplayLink(target: self, selector: #selector(receive(sender:))) displayLink.add(to: runLoop, forMode: mode) displayLink.preferredFramesPerSecond = preferredFramesPerSecond self.displayLink = displayLink } func cancel() { displayLink?.invalidate() displayLink = nil } } }
  33. extension CADisplayLink { final class Subscription<S: Subscriber>: Combine.Subscription where S.Input

    == CADisplayLink { let combineIdentifier = CombineIdentifier() let subscriber: S let runLoop: RunLoop let mode: RunLoop.Mode let preferredFramesPerSecond: Int private var displayLink: CADisplayLink? @objc func receive(sender: CADisplayLink) { _ = subscriber.receive(sender) } func request(_ demand: Subscribers.Demand) { let displayLink = CADisplayLink(target: self, selector: #selector(receive(sender:))) displayLink.add(to: runLoop, forMode: mode) displayLink.preferredFramesPerSecond = preferredFramesPerSecond self.displayLink = displayLink } func cancel() { displayLink?.invalidate() displayLink = nil } } } ※ initলུ Subscriptionͷࣝผࢠ
  34. extension CADisplayLink { final class Subscription<S: Subscriber>: Combine.Subscription where S.Input

    == CADisplayLink { let combineIdentifier = CombineIdentifier() let subscriber: S let runLoop: RunLoop let mode: RunLoop.Mode let preferredFramesPerSecond: Int private var displayLink: CADisplayLink? @objc func receive(sender: CADisplayLink) { _ = subscriber.receive(sender) } func request(_ demand: Subscribers.Demand) { let displayLink = CADisplayLink(target: self, selector: #selector(receive(sender:))) displayLink.add(to: runLoop, forMode: mode) displayLink.preferredFramesPerSecond = preferredFramesPerSecond self.displayLink = displayLink } func cancel() { displayLink?.invalidate() displayLink = nil } } } ※ initলུ Subscriptionଆ͔Β஋Λྲྀͨ͢ΊsubscriberΛ౉͢
  35. extension CADisplayLink { final class Subscription<S: Subscriber>: Combine.Subscription where S.Input

    == CADisplayLink { let combineIdentifier = CombineIdentifier() let subscriber: S let runLoop: RunLoop let mode: RunLoop.Mode let preferredFramesPerSecond: Int private var displayLink: CADisplayLink? @objc func receive(sender: CADisplayLink) { _ = subscriber.receive(sender) } func request(_ demand: Subscribers.Demand) { let displayLink = CADisplayLink(target: self, selector: #selector(receive(sender:))) displayLink.add(to: runLoop, forMode: mode) displayLink.preferredFramesPerSecond = preferredFramesPerSecond self.displayLink = displayLink } func cancel() { displayLink?.invalidate() displayLink = nil } } } ※ initলུ ड͚ͨCADisplayLinkΛsubscriberʹྲྀ͢selector༻ͷmethod
  36. extension CADisplayLink { final class Subscription<S: Subscriber>: Combine.Subscription where S.Input

    == CADisplayLink { let combineIdentifier = CombineIdentifier() let subscriber: S let runLoop: RunLoop let mode: RunLoop.Mode let preferredFramesPerSecond: Int private var displayLink: CADisplayLink? @objc func receive(sender: CADisplayLink) { _ = subscriber.receive(sender) } func request(_ demand: Subscribers.Demand) { let displayLink = CADisplayLink(target: self, selector: #selector(receive(sender:))) displayLink.add(to: runLoop, forMode: mode) displayLink.preferredFramesPerSecond = preferredFramesPerSecond self.displayLink = displayLink } func cancel() { displayLink?.invalidate() displayLink = nil } } } ※ initলུ ඞཁʹԠͯ͡BackpressureʹରԠͤ͞Δ requestͰCADisplayLinkΛ࣮ߦ
  37. extension CADisplayLink { final class Subscription<S: Subscriber>: Combine.Subscription where S.Input

    == CADisplayLink { let combineIdentifier = CombineIdentifier() let subscriber: S let runLoop: RunLoop let mode: RunLoop.Mode let preferredFramesPerSecond: Int private var displayLink: CADisplayLink? @objc func receive(sender: CADisplayLink) { _ = subscriber.receive(sender) } func request(_ demand: Subscribers.Demand) { let displayLink = CADisplayLink(target: self, selector: #selector(receive(sender:))) displayLink.add(to: runLoop, forMode: mode) displayLink.preferredFramesPerSecond = preferredFramesPerSecond self.displayLink = displayLink } func cancel() { displayLink?.invalidate() displayLink = nil } } } ※ initলུ ߪಡଆͰΩϟϯηϧ͞Εͨͱ͖ͷڍಈ
  38. extension CADisplayLink { struct Publisher: Combine.Publisher { typealias Output =

    CADisplayLink typealias Failure = Never var runLoop: RunLoop var mode: RunLoop.Mode var preferredFramesPerSecond: Int func receive<S: Subscriber>(subscriber: S) where Failure == S.Failure, Output == S.Input { let subscription = Subscription( subscriber: subscriber, runLoop: runLoop, mode: mode, preferredFramesPerSecond: preferredFramesPerSecond ) subscriber.receive(subscription: subscription) } } }
  39. ࠓճ͸׬ྃΠϕϯτ͕ͳ͍ͷͰলུ

  40. extension CADisplayLink { static func publish(on runLoop: RunLoop, forMode mode:

    RunLoop.Mode, preferredFramesPerSecond: Int) -> Publisher { Publisher(runLoop: runLoop, mode: mode, preferredFramesPerSecond: preferredFramesPerSecond) } }
  41. 173153.771382711 173153.78804937802 173153.804716045 173153.82138271202 173153.838049379 173153.85471604601 . . . let

    cancellable = CADisplayLink.publish( on: .main, forMode: .default, preferredFramesPerSecond: 60 ) .sink(receiveValue: { print($0.timestamp) }) self.cancellable = AnyCancellable(cancellable) AnyCancellable͸deinit࣌ʹࣗಈcancel͞ΕΔͷͰ஫ҙ
  42. ɾbuilt-inͰఏڙ͞ΕΔͷ͸FoundationͷҰ෦APIͷΈ ※1 ɾPublisher͸ઐ༻ܕΛఆٛͨ͠΄͏͕Αͦ͞͏ ※1 RealityKitͳͲ΋Ұ෼ରԠ ɾͳͷͰUIKitͳͲطଘͷFramework͸ࣗલͰ౷߹ͤ͞Δ ɾͱ͸͍͑ຖճPublisherɺSubscriptionΛఆٛ͢Δͷ͸໘౗ ·ͱΊ

  43. Thanks