Combine入門

 Combine入門

AppleのCombineフレームワークの基本的な内容について

E45f9c343d90c74554c65c89c6f861bc?s=128

shtnkgm

June 11, 2020
Tweet

Transcript

  1. ! Combineೖ໳ 2020.6.11 iOSLT / @shtnkgm 1

  2. Combineͱ͸ • Swift޲͚Framework • WWDC2019Ͱൃද • iOS 13.0+ • ඇಉظॲཧΛѻ͏΋ͷ

    2
  3. UIKitͷඇಉظॲཧ • Taregt/ActionʢTimer, @IBAction ...ʣ • NotificationCenter • URLSession •

    KVOʢKey-value observingʣ • Callback࣮૷ʢDelegate, Closureʣ 3
  4. ඇಉظॲཧʹ͓͚Δ՝୊ • ωετͨ͠Closure Callback஍ࠈ • Delegateॻ͘ͷ΋ಡΉͷ΋ΊΜͲ͍͘͞஍ࠈ • ඇಉظΠϕϯτͷछྨ΋࣮૷΋͍Ζ͍ΖɺྲྀΕ͕Θ͔Βͳ͘ͳ Δ 4

  5. Combine͸͜ΕΒͷඇಉظॲཧΛ Ұݩతɺએݴతʹѻ͏ͨΊͷAPI 5

  6. ओͳAPI • Publisher • Subscriber • Operator 6

  7. Publisher • ஋΍Τϥʔ͕ͲͷΑ͏ʹൃੜ͢Δ͔Λఆٛ • Subscriberͷొ࿥ΛՄೳʹ͢Δʢߪಡʣ • ࣮૷͸஋ܕʢstructʣ 7

  8. Publisherͷఆٛ protocol Publisher { associatedtype Output associatedtype Failure: Error //

    Subscriberͷొ࿥ func subscribe<S>(_ subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input } 8
  9. NotificationCenterͰͷPublisher࣮૷ྫ extension NotificationCenter { struct Publisher: Combine.Publisher { typealias Output

    = Notifiation typealias Failure = Never // Τϥʔͳ͍৔߹͸Neverܕ init(center: NotificationCenter, name: Notification.Name, object: Any? = nil) } } 9
  10. ஋ฦ͞ͳ͍ͷͬͯVoid͡Όͳ͍ͷʁ 10

  11. Neverܕ public enum Never {} • ࣮૷͸caseͳ͠enum • ͭ·Γ஋Λͭ͘Εͳ͍͜ͱΛදݱ 11

  12. Voidܕ public typealias Void = () • ࣮૷͸ۭͷλϓϧͷΤΠϦΞε • ͭ·ΓۭͷλϓϧΛฦ͍ͯ͠Δ

    12
  13. fatalErrorͷ໭Γ஋΋Neverܕ class ViewController: UIViewController init() { ... } required init?(coder

    aDecoder: NSCoder) { // NeverܕΛฦ͢͜ͱͰϓϩάϥϜऴྃ fatalError("init(coder:) has not been implemented") } func hoge() -> Int { // ໭Γ஋ͷܕݕࠪ΋ߦΘΕͳ͍ͷͰ // ͜ΕͰίϯύΠϧ͕௨Δ fatalError() } } 13
  14. Subscriber • ஋ͱ׬ྃΛड͚औΔ • ࢀরܕʢclassʣ 14

  15. Subscriberͷఆٛ protocol Subscriber { associatedtype Input associatedtype Failure : Error

    // SubscriptionΛड͚औΔ func receive(subscription: Subscription) // ೖྗ஋Λड͚औΔ func receive(_ input: Self.Input) -> Subscribers.Demand // ׬ྃΛड͚औΔʢ༗ݶͷ৔߹ʣ func receive(completion: Subscribers.Completion<Self.Failure>) } 15
  16. Subscriberͷ࣮૷ྫʢAssignʣ ࢦఆ͞ΕͨΩʔύεʹ஋Ληοτ͢ΔSubscriber // Subscribers͸ͨͩͷnamespaceͱͯ͠ͷenum // AssignͷଞʹɺDemand, Completion, Sink͕͋Δ extension Subscribers

    { class Assign<Root, Input>: Subscriber, Cancellable { typealias Faiure = Never init(object: Root, keyPath: ReferenceWritableKeyPath<Root, Input>) } } 16
  17. Operator • Publisherϓϩτίϧʹ४ڌ • ஋͕มԽ͢Δ;Δ·͍Λఆٛ • ஋ܕʢstructʣ 17

  18. Operatorͷ࣮૷ྫʢMapʣ // ͜Ε΋namespace // MapҎ֎ʹ΋Sequence, Catch, ReceiveOn, SubscribeOn...ͳͲΊͬͪΌ͋Δ extension Publishers

    { struct Map<Upstream, Output> where Upstream : Publisher { typealias Failure = Upstream.Failure let upstream: Upstream let transform: (Upstream.Output) -> Output } } 18
  19. OperatorͰͰ͖Δ͜ͱͷҰྫ • ؔ਺తͳ஋ͷม׵ • Ϧετૢ࡞ • ΤϥʔϋϯυϦϯά • εϨουɺΩϡʔΠϯάॲཧʢαϒεϨουॲཧʣ •

    εέδϡʔϦϯάʢλΠϚʔॲཧʣ 19
  20. Publisher, Subscriber, OperatorΛ࢖ͬͯΈΔ ຐ๏ֶߍͱֶ೥ class Wizard { var grade: Int

    init(grade: Int) { self.grade = grade } } let merlin = Wizard(grade: 5) 20
  21. Publisher, Subscriber, OperatorΛ࢖ͬͯΈΔ let graduationPublisher = NotificationCenter.Publisher(center: .default, name: .graduated,

    object: merlin) let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade) let converter = Publishers.Map(upstream: graduationPublisher) { note in return note.userInfo?["NewGrade"] as? Int ?? 0 } converter.subscribe(gradeSubscriber) 21
  22. Publisherͷmap, assign֦ு extension Publisher { func map<T>(_ transform: @escaping (Self.Output)

    -> T) -> Publishers.Map<Self, T> { return Publishers.Map(upstream: self, transform: transform) } } extension Publisher where Self.Failure == Never { func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable { return Subscribers.Assign<Self, Input>(object: object, keyPath: keyPath) } } 22
  23. ϝιουνΣΠϯͰΑΓએݴతʹ let cancellable = NotificationCenter.default.publisher(for: .graduated, object: merlin) .map {

    note in return note.userInfo?["NewGrade"] as? Int ?? 0 } .assign(to: \.grade, on: merlin) 23
  24. ϝιουνΣΠϯͰΑΓએݴతʹ compactMap΍filterɺprefixͳͲArrayͰݟ׳Εͨؔ਺΋ let cancellable = NotificationCenter.default.publisher(for: .graduated, object: merlin) .compactMap

    { note in return note.userInfo?["NewGrade"] as? Int } .filter { $0 >= 5 } .prefix(3) .assign(to: \.grade, on: merlin) 24
  25. ௨৴ॲཧʹ࢖͑ͦ͏ͳ΋ͷ΋ଟ͍ https://developer.apple.com/documentation/combine/publishers • Zip, Zip3, Zip4, CombineLatest: ଴ͪ߹Θͤ • Decode:

    σίʔυॲཧ • Retry: ϦτϥΠ • Throttle, Debounce: ࿈ଓ͢ΔΠϕϯτΛݮΒ͢ʢ࿈ଧɺΠϯΫϦϝϯλϧαʔ νʣ • Timeout: λΠϜΞ΢τ 25
  26. ௨৴ॲཧͷ࣮૷ྫ 26

  27. class UserRepository { private var subscriptions = Set<AnyCancellable>() func fetchUsers()

    -> Future<[User], UserAPIError> { return Future<[User], UserAPIError> { [unowned self] promise in URLSession .shared .dataTaskPublisher(for: url) .debounce(for: .milliseconds(500), scheduler: RunLoop.main) .retry(3) .map { $0.data } .decode(type: [User].self, decoder: JSONDecoder()) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { _ in promise(.failure(.somethingWrong)) }, receiveValue: { users in promise(.success(users)) }) .store(in: &self.subscriptions) } } func cancelAll() { subscriptions.forEach { $0.cancel() } } } 27
  28. Viewͱͷ࿈ܞ • SwiftUI • ObservableObject/Published • UIKit • UICollectionViewDiffableDataSource •

    UITableViewDiffableDataSource 28
  29. noppefoxwolf/Combinative • https://github.com/noppefoxwolf/Combinative • UIKitͷUIControllʹCombineΛ֦ு let button = UIButton() button.cmb.tap.sink

    { (button) in // do something } @IBOutlet weak var textField: UITextField! textField.cmb.text.sink { (text) in print(text) } 29
  30. ͓ΘΓ 30