Slide 1

Slide 1 text

! Combineೖ໳ 2020.6.11 iOSLT / @shtnkgm 1

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

UIKitͷඇಉظॲཧ • Taregt/ActionʢTimer, @IBAction ...ʣ • NotificationCenter • URLSession • KVOʢKey-value observingʣ • Callback࣮૷ʢDelegate, Closureʣ 3

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

ओͳAPI • Publisher • Subscriber • Operator 6

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Publisherͷఆٛ protocol Publisher { associatedtype Output associatedtype Failure: Error // Subscriberͷొ࿥ func subscribe(_ subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input } 8

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

஋ฦ͞ͳ͍ͷͬͯVoid͡Όͳ͍ͷʁ 10

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

fatalErrorͷ໭Γ஋΋Neverܕ class ViewController: UIViewController init() { ... } required init?(coder aDecoder: NSCoder) { // NeverܕΛฦ͢͜ͱͰϓϩάϥϜऴྃ fatalError("init(coder:) has not been implemented") } func hoge() -> Int { // ໭Γ஋ͷܕݕࠪ΋ߦΘΕͳ͍ͷͰ // ͜ΕͰίϯύΠϧ͕௨Δ fatalError() } } 13

Slide 14

Slide 14 text

Subscriber • ஋ͱ׬ྃΛड͚औΔ • ࢀরܕʢclassʣ 14

Slide 15

Slide 15 text

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) } 15

Slide 16

Slide 16 text

Subscriberͷ࣮૷ྫʢAssignʣ ࢦఆ͞ΕͨΩʔύεʹ஋Ληοτ͢ΔSubscriber // Subscribers͸ͨͩͷnamespaceͱͯ͠ͷenum // AssignͷଞʹɺDemand, Completion, Sink͕͋Δ extension Subscribers { class Assign: Subscriber, Cancellable { typealias Faiure = Never init(object: Root, keyPath: ReferenceWritableKeyPath) } } 16

Slide 17

Slide 17 text

Operator • Publisherϓϩτίϧʹ४ڌ • ஋͕มԽ͢Δ;Δ·͍Λఆٛ • ஋ܕʢstructʣ 17

Slide 18

Slide 18 text

Operatorͷ࣮૷ྫʢMapʣ // ͜Ε΋namespace // MapҎ֎ʹ΋Sequence, Catch, ReceiveOn, SubscribeOn...ͳͲΊͬͪΌ͋Δ extension Publishers { struct Map where Upstream : Publisher { typealias Failure = Upstream.Failure let upstream: Upstream let transform: (Upstream.Output) -> Output } } 18

Slide 19

Slide 19 text

OperatorͰͰ͖Δ͜ͱͷҰྫ • ؔ਺తͳ஋ͷม׵ • Ϧετૢ࡞ • ΤϥʔϋϯυϦϯά • εϨουɺΩϡʔΠϯάॲཧʢαϒεϨουॲཧʣ • εέδϡʔϦϯάʢλΠϚʔॲཧʣ 19

Slide 20

Slide 20 text

Publisher, Subscriber, OperatorΛ࢖ͬͯΈΔ ຐ๏ֶߍͱֶ೥ class Wizard { var grade: Int init(grade: Int) { self.grade = grade } } let merlin = Wizard(grade: 5) 20

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Publisherͷmap, assign֦ு extension Publisher { func map(_ transform: @escaping (Self.Output) -> T) -> Publishers.Map { return Publishers.Map(upstream: self, transform: transform) } } extension Publisher where Self.Failure == Never { func assign(to keyPath: ReferenceWritableKeyPath, on object: Root) -> AnyCancellable { return Subscribers.Assign(object: object, keyPath: keyPath) } } 22

Slide 23

Slide 23 text

ϝιουνΣΠϯͰΑΓએݴతʹ 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

Slide 24

Slide 24 text

ϝιουνΣΠϯͰΑΓએݴతʹ 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

Slide 25

Slide 25 text

௨৴ॲཧʹ࢖͑ͦ͏ͳ΋ͷ΋ଟ͍ https://developer.apple.com/documentation/combine/publishers • Zip, Zip3, Zip4, CombineLatest: ଴ͪ߹Θͤ • Decode: σίʔυॲཧ • Retry: ϦτϥΠ • Throttle, Debounce: ࿈ଓ͢ΔΠϕϯτΛݮΒ͢ʢ࿈ଧɺΠϯΫϦϝϯλϧαʔ νʣ • Timeout: λΠϜΞ΢τ 25

Slide 26

Slide 26 text

௨৴ॲཧͷ࣮૷ྫ 26

Slide 27

Slide 27 text

class UserRepository { private var subscriptions = Set() 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

Slide 28

Slide 28 text

Viewͱͷ࿈ܞ • SwiftUI • ObservableObject/Published • UIKit • UICollectionViewDiffableDataSource • UITableViewDiffableDataSource 28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

͓ΘΓ 30