Slide 1

Slide 1 text

Combine Overview CombineΰϦΰϦΩϟονΞοϓձ Daiki Matsudate @d_date iOS Developer

Slide 2

Slide 2 text

Daiki Matsudate • Tokyo, Japan • iOS Developer from iOS 4 • Independent Developer • @d_date • Sushi

Slide 3

Slide 3 text

March, 18 - 20th, 2020 https://www.tryswift.co/

Slide 4

Slide 4 text

A unified, declarative API for 
 processing values over time

Slide 5

Slide 5 text

Why Reactive Programing?

Slide 6

Slide 6 text

Why Reactive Programing? • ෳ਺ͷඇಉظ (Asynchronous) ͳλεΫΛͲ͏ѻ͏͔ • Ͳ͏ѻ͏͔ • Ͳͷॱ൪Ͱʁ • ௚ྻ / ฒྻ? • ్தͰΤϥʔ͕ൃੜͨ͠Βʁ

Slide 7

Slide 7 text

Reactive Programing

Slide 8

Slide 8 text

Reactive Programming • Observer Pattern • ΠϕϯτͷൃՐΛϋϯυϦϯά͍ͨ͠ͱ͜ΖͰ؂ࢹ͢Δ • Operators • ΠϕϯτΛՃ޻͢Δ • Open Source • RxSwift / Reactive Swift

Slide 9

Slide 9 text

https://twitter.com/diegopetrucci/status/1135655480825655297

Slide 10

Slide 10 text

Combine

Slide 11

Slide 11 text

Combine • Declarative Swift API • ඇಉظͳΠϕϯτΛܕͱͯ͠දݱ • ଟछଟ༷ͳԋࢉࢠͰΠϕϯτΛϋϯυϦϯά • Reactive Framework by Apple

Slide 12

Slide 12 text

Publisher

Slide 13

Slide 13 text

Publisher • Value΍ErrorΛੜ੒͢Δ • ొ࿥͞ΕͨSubscriberʹValue΍ErrorΛૹ৴͢Δ

Slide 14

Slide 14 text

public protocol Publisher { /// The kind of values published by this publisher. associatedtype Output /// The kind of errors this publisher might publish. /// Use `Never` if this `Publisher` does not publish errors. associatedtype Failure : Error /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` func receive(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input } Publisher

Slide 15

Slide 15 text

@Published @propertyWrapper public struct Published { /// Initialize the storage of the Published property as well as the corresponding `Publisher`. public init(initialValue: Value) public class Publisher : Publisher { /// The kind of values published by this publisher. public typealias Output = Value /// The kind of errors this publisher might publish. /// /// Use `Never` if this `Publisher` does not publish errors. public typealias Failure = Never /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` public func receive(subscriber: S) where Value == S.Input, S : Subscriber, S.Failure == Published.Publisher.Failure } /// The property that can be accessed with the `$` syntax and allows access to the `Publisher` public var projectedValue: Published.Publisher { mutating get } }

Slide 16

Slide 16 text

Default Publishers • Notification Center • URLSession • Just, Future • CurrentValueSubject, PassthroughSubject • NSManagedObject let ncpublisher = NotificationCenter.Publisher(center: .default, name: .NSCalendarDayChanged) let sessionPublisher = URLSession(configuration: .default) .dataTaskPublisher(for: URL(string: “https://api.google.com")!) let managedObject = NSManagedObject(context: context).publisher(for: \.hasChanges) let just = Just("") let future = Future { (result) in result(.success("result")) result(.failure(TypedError.example)) }

Slide 17

Slide 17 text

From WWDC19 Introducing Combine One Many Synchronous Asynchronous Int Array Future Publisher

Slide 18

Slide 18 text

Subscriber

Slide 19

Slide 19 text

Publisher • Publisher͕ൃߦͨ͠஋΍Error, completionΛߪಡ͢Δ • ߪಡΛΩϟϯηϧͰ͖ΔʢCancellable)

Slide 20

Slide 20 text

public protocol Subscriber : CustomCombineIdentifierConvertible { /// The kind of values this subscriber receives. associatedtype Input /// The kind of errors this subscriber might receive. /// Use `Never` if this `Subscriber` cannot receive errors. associatedtype Failure : Error /// Tells the subscriber that it has successfully subscribed to the publisher and may request items. /// /// Use the received `Subscription` to request items from the publisher. func receive(subscription: Subscription) /// Tells the subscriber that the publisher has produced an element. func receive(_ input: Self.Input) -> Subscribers.Demand /// Tells the subscriber that the publisher has completed publishing, either normally or with an error. func receive(completion: Subscribers.Completion) } Subscriber

Slide 21

Slide 21 text

Assign

Slide 22

Slide 22 text

let labelTextSubscriber = Subscribers.Assign(object: label, keyPath: \.text) $queryPublisher .map { "searching for \($0)..." } .map(Optional.init) .subscribe(labelTextSubscriber) labelTextSubscriber.cancel() Assign

Slide 23

Slide 23 text

let cancellable = $queryPublisher .map { "searching for \($0)..." } .assign(to: \.text, on: label) // how to cancel cancellable.cancel() Assign

Slide 24

Slide 24 text

var cancellables = [AnyCancellable]() $queryPublisher .map { "searching for \($0)..." } .assign(to: \.text, on: label) .store(in: &cancellables) Assign

Slide 25

Slide 25 text

Sink

Slide 26

Slide 26 text

let labelTextSubscriber = Subscribers.Sink(receiveCompletion: { _ in }) { self.label.text = $0 } $queryPublisher .map { "searching for \($0)..." } .subscribe(labelTextSubscriber) labelTextSubscriber.cancel() Sink

Slide 27

Slide 27 text

let cancellable = $queryPublisher .map { "searching for \($0)..." } .sink { self.label.text = $0 } cancellable.cancel() Sink

Slide 28

Slide 28 text

var cancellables = [AnyCancellable]() $queryPublisher .map { "searching for \($0)..." } .sink { self.label.text = $0 } .store(in: &cancellables) Sink

Slide 29

Slide 29 text

subscribe(on:) vs. receive(on:)

Slide 30

Slide 30 text

subscribe(on:) vs. receive(on:) • subscribe(on:) ͸ upstream messagesʹӨڹ • receive(on:) ͸downstream messagesͷ࣮ߦίϯςΩετʹӨڹ • ͜ͷྫͰ͸ɺpublisher΁ͷϦΫΤετ͸backgroundQueueͰߦΘΕΔ ͕ɺΤϨϝϯτΛड͚औΔͷ͸ϝΠϯεϨου let backgroundQueue = DispatchQueue(label: "background", qos: .utility) $queryPublisher .map { "searching for \($0)..." } .subscribe(on: backgroundQueue) .receive(on: DispatchQueue.main) .sink { self.label.text = $0 } .store(in: &cancellables)

Slide 31

Slide 31 text

The Pattern Subscriber is attached to Publisher Publisher sends a Subscription Subscriber requests N values Publisher sends N values or less Publisher sends completion Publisher Subscriber subscribe( ) receive(subscription:) request(_ : Demand) receive(completion:) receive(_ : Input) receive(_ : Input) • • •

Slide 32

Slide 32 text

Subject

Slide 33

Slide 33 text

Subject • PublisherͱSubscriberͷ྆ํͷੑ࣭Λ΋ͭ • ෳ਺ͷSubscriber΁஋ΛϒϩʔυΩϟετ͢Δ

Slide 34

Slide 34 text

Kinds of Subjects Passthrough CurrentValue

Slide 35

Slide 35 text

Kinds of Subjects Passthrough CurrentValue

Slide 36

Slide 36 text

Binding to SwiftUI

Slide 37

Slide 37 text

// Combine with SwiftUI class WizardModel : BindableObject { var trick: WizardTrick { didSet { didChange.send() } var wand: Wand? { didSet { didChange.send() } let didChange = PassthroughSubject() } struct TrickView: View { @ObjectBinding var model: WizardModel var body: some View { Text(model.trick.name) } }

Slide 38

Slide 38 text

Operator

Slide 39

Slide 39 text

catch abortOnError allSatisfy append breakpoint breakpointOnError combineLatest compactMap count dropFirst filter first handleEvents ignoreOutput last log mapError max min output prefix prepend print removeDuplicates replaceEmpty replaceError replaceNil retry scan setFailureType switchToLatest zip map contains flatMap reduce merge drop collect From WWDC19 Introducing Combine

Slide 40

Slide 40 text

Error Handling

Slide 41

Slide 41 text

Failure Handling Operators assertNoFailure retry catch mapError setFailureType

Slide 42

Slide 42 text

https://github.com/freak4pc/rxswift-to-combine-cheatsheet/blob/master/Data/core_components.csv RxSwift to Combine

Slide 43

Slide 43 text

WWDC Sessions Introducing Combine WWDC19 Combine in Practice WWDC19 Data Flow through SwiftUI WWDC19

Slide 44

Slide 44 text

Great Presentations • https://speakerdeck.com/riteshhh/combine-all-the-things