Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting Started with Combine

Getting Started with Combine

Getting Started with Combine, as presented by Shai Mishali in various conferences.

Shai Mishali

October 02, 2020
Tweet

More Decks by Shai Mishali

Other Decks in Technology

Transcript

  1. iOS Tech Lead @ Gett Open Source ❤ Hackathon fan

    and winner of some International Speaker @freak4pc Ab t Me
  2. Involved in dozens of tutorials and several published books Author

    & Editor at raywenderlich.com iOS Team @freak4pc Ab t Me
  3. “The Combine framework provides a declarative Swift API for processing

    values over time. These values can represent many kinds of asynchronous events…” What is Combine?# $
  4. “The Combine framework provides a declarative Swift API for processing

    values over time. These values can represent many kinds of asynchronous events…” What is Combine?# $
  5. (almost) Everything is Asynchronous Notification Center
 % KVO
 & Delegates

    ' Bindings
 ↔ UI
 Events
 ) Networking
 * Publisher !
  6. (almost) Everything is Asynchronous Consumers can Subscribe to publishers to

    be notified on results of that work Publisher Subscriber Hey! I perform work and emit values as they change Cool, I’d like to receive these updated values!
  7. If all asynchronous work is represented under the same contract,

    composition becomes extremely powerful. Publisher all the things tap on a button @IBAction user/pass from UITextField(s) .text network request URLSession + Closure tap on a button Publisher user/pass from UITextField(s) Publisher network request Publisher Different interfaces Constant context switching and juggling Same interfaces Composition, unification
  8. Subscribing to a piece of information always pushes the latest

    value from the source, letting you keep your app up to date. Reactive and Declarative let canMakePurchase = productPrice .combineLatest(userBalance) .map { $0 !" $1 }!# Publisher of Bool canMakePurchase ALWAYS reflects the latest state canMakePurchase doesn’t reflect the latest state var userBalance = 5 let productPrice = 10 let canMakePurchase = userBalance !" productPrice print(canMakePurchase) userBalance += 20 print(canMakePurchase) !# false !# STILL false!
  9. A type that can emit a sequence of values over

    time , Publisher Type of values this publisher emits Type of error this publisher emits Publisher<Output, Failure: Error>
  10. Publishers can emit 3 types of events Finished Failure (Failure)

    Completion: Value (Output) Value: , Publisher
  11. Publishers may emit values as long as they’re not finished

    with a completion event Any completion event terminates the publisher and no further values will be published 1 1 2 3 5 8 13 X Publisher<Int, MyError> 0 1 1 2 3 5 8 13 0 Completed with no error Completed with MyError , Publisher
  12. a Publisher is push-based interface, which delivers its values to

    one or more Subscribers based on their demand - Subscriber Type of values this subscriber receives Type of error this publisher emits Subscriber<Input, Failure: Error>
  13. - Subscriber Subscriber<Input, Failure> Publisher<Output, Failure> error type must match

    output type must match a Publisher is push-based interface, which delivers its values to one or more Subscribers based on their demand
  14. The only thing missing is a piece to connect the

    Subscriber(s) to the Publisher so they can communicate with each other - Subscriber Publisher Subscriber ???
  15. A subscription represents the connection between a publisher and a

    subscriber and is where most of the “heavy work” happens . Subscription Publisher Subscriber Subscription
  16. The publishers provides a Subscription to the Subscriber via its

    receive(subscription:) method . Subscription Publisher Subscriber receive(subscription:) Subscription
  17. The subscription pushes events to subscribers via 2 different receive

    methods Subscriber<Input, Failure> receive(Input) receive(completion: .finished) receive(completion: .failure(Failure)) Values: Completion Event: . Subscription
  18. Once you’re done with the specific subscription, you can cancel

    it using its cancel() method or deallocate the subscription . Subscription cancel() Canceling a subscription will release any memory and resources allocated by the attached subscriber Subscription X
  19. Backpressure revolves around strategies to deal with Publishers that emit

    too many values, too rapidly. 0 Backpressure Publisher Subscriber 0 1 2 3 4 5 … 10000 10,000 values over 1 second 12 3
  20. The control is transitioned to the Subscriber, which demands a

    specific amount of values it’s willing to handle 0 Backpressure Publisher Subscriber I’d like to receive up to X values Here are values! I’ll never give you more than you demand Based on these new values, I’d like to adjust my Demand 1 2 3 The publisher must respect the contract and only produce values up to the Subscriber’s demand Types of Demand: Demand.unlimited Demand.none Demand.max(x)
  21. A Subject is a special kind of publisher you can

    manually send values to, which is quite useful for bridging imperative code 4 Subject Publisher Subject Immutable (read-only) Values produced internally by the publisher Mutable (read/write)
 Values can be manually pushed by consumer
  22. There are two kinds of Subjects - PassthroughSubject and CurrentValueSubject

    4 Subject PassthroughSubject Useful for representing events let tappedButton = PassthroughSubject<Void, Never>() // Send a “tap” to the Subject, existing subscribers // will immediately receive it tappedButton.send() New subscribers get any values sent after the subscription
  23. There are two kinds of Subjects - PassthroughSubject and CurrentValueSubject

    4 Subject CurrentValueSubject Useful for representing state // Takes an initial value let isLoading = CurrentValueSubject<Bool, Never>(false) // Send a value to the subject, like before isLoading.send(true) // Or via the `value` setter isLoading.value = false New subscribers get a replay of the current value
  24. ⏳ tl;dr Publisher Subscriber App Subscriber attaches to Publisher Publisher.subscribe(_:)

    Publisher provides Subscription to Subscriber receive(subscription:) Subscriber is provided to Publisher receive(subscriber:) 1 2 3 Subscription First, the Subscriber and Publisher are connected. Once they’re connected, the Publisher provides a Subscription to the Subscriber.
  25. Subscriber Subscription At this point, the Subscriber and Subscription interact

    amongst themselves on how many values the Subscription may produce Hey there, please give me up to X values request(Subscribers.Demand) Here are some values! receive(value:) "# Subscribers.Demand Subscriber may change demand based on the value in an additive way I’m done! Here’s a completion event receive(finished:) 1 2 3 ⏳ tl;dr
  26. So now I have a bunch of Publishers. What do

    I actually do with them? , Consuming Publishers #$
  27. There are two built-in ways to consume Publishers: sink and

    assign , Consuming Publishers let publisher = ["hey", "there", “Swift”, “Heroes”].publisher publisher .sink(receiveCompletion: { print("Completed with \($0)" )}, receiveValue: { print("Got value: \($0)") }) sink is a closure-based subscriber: Output: Got value: hey Got value: there Got value: Swift Got value: Heroes Completed with finished
  28. , Consuming Publishers assign pushes every emission to the provided

    key path let publisher = ["hey", "there", "Swift", “Heroes”].publisher publisher .assign(to: \.text, on: textField) Swift there Hey Heroes Publisher assign UITextField.text 1 2 3 4 There are two built-in ways to consume Publishers: sink and assign
  29. Getting values is useful, but don’t you wish you could

    control and modify the values emitted by publishers? , Consuming Publishers ⁉
  30. 8 Operators If Combine would be a language, operators are

    the words. The better your vocabulary will be, the more things you can do with your data. Operators let you manipulate upstream publishers and control the emissions in extremely expressive and flexible ways Publisher Modified Publisher Operator
  31. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2” “1.23” “2”
  32. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2” “1.23” “2”
  33. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 1.23 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2” “2”
  34. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 1.23 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2” “2”
  35. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 1.23 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2” “2”
  36. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) 2.0 “2.0” “try!” “Swift” “0.2” “2”
  37. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) 2.0 “2.0” “try!” “Swift” “0.2” “2”
  38. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) 2.0 “2.0” “try!” “Swift” “0.2” “2”
  39. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2” “2”
  40. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2” “2”
  41. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2” “2”
  42. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) 2.0 “2.0” “try!” “Swift” “0.2”
  43. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) 2.0 “2.0” “try!” “Swift” “0.2”
  44. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) 2.0 “2.0” “try!” “Swift” “0.2”
  45. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: .append(stride(from: 0.3, to: 0.6, by: 0.1)) 2.0 “2.0” “try!” “Swift” “0.2”
  46. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2”
  47. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “2.0” “try!” “Swift” “0.2”
  48. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “try!” “Swift” “0.2” 2.0
  49. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “try!” “Swift” “0.2” 2.0
  50. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “try!” “Swift” “0.2” 2.0
  51. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “try!” “Swift” “0.2”
  52. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “try!” “Swift” “0.2”
  53. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “0.2” nil nil
  54. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “0.2”
  55. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) “0.2”
  56. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) 0.2
  57. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) 0.2
  58. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) 0.2
  59. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) 1.0
  60. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) 1.0
  61. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) 1.0
  62. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 .append(stride(from: 0.3, to: 0.6, by: 0.1)) 1.0
  63. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 1.0
  64. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 1.0 0.3 0.4 0.5
  65. 8 Operators Simple, chainable methods with many same-named variants in

    the Swift standard library. Publisher<String, Never> publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) Output: 2.0 1.0 0.3 0.4 0.5 Finished
  66. 8 Operators Operators are publishers themselves Each operator’s type wraps

    the previous one in the chain publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) // Publishers.CompactMap<AnyPublisher<String, Never>, Float> // Publishers.RemoveDuplicates<Publishers.CompactMap<AnyPublisher<String… // Publishers.Map<Publishers.RemoveDuplicates<Publishers.CompactMap<AnyPubl… // Publishers.Filter<Publishers.Map<Publishers.RemoveDuplicates<Publishers.CompactM… // Publishers.Drop<Publishers.Filter<Publishers.Map<Publishers.RemoveDuplicates<Publishers.Co…
  67. 8 Operators Operators are publishers themselves Each operator’s type wraps

    the previous one in the chain publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) // Publishers.CompactMap<AnyPublisher<String, Never>, Float> // Publishers.RemoveDuplicates<Publishers.CompactMap<AnyPublisher<String… // Publishers.Map<Publishers.RemoveDuplicates<Publishers.CompactMap<AnyPubl… // Publishers.Filter<Publishers.Map<Publishers.RemoveDuplicates<Publishers.CompactM… // Publishers.Drop<Publishers.Filter<Publishers.Map<Publishers.RemoveDuplicates<Publishers.Co… Publishers.Concatenate<Publishers.Drop<Publishers.Filter<Publ ishers.Map<Publishers.RemoveDuplicates<Publishers.CompactMap< AnyPublisher<String, Never>, %oat &, %oat '
  68. 8 Operators Operators are publishers themselves Each operator’s type wraps

    the previous one in the chain publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) // Publishers.CompactMap<AnyPublisher<String, Never>, Float> // Publishers.RemoveDuplicates<Publishers.CompactMap<AnyPublisher<String… // Publishers.Map<Publishers.RemoveDuplicates<Publishers.CompactMap<AnyPubl… // Publishers.Filter<Publishers.Map<Publishers.RemoveDuplicates<Publishers.CompactM… // Publishers.Drop<Publishers.Filter<Publishers.Map<Publishers.RemoveDuplicates<Publishers.Co… Publishers.Concatenate<Publishers.Drop<Publishers.Filter<Publ ishers.Map<Publishers.RemoveDuplicates<Publishers.CompactMap< AnyPublisher<String, Never>, %oat &, %oat ' 3
  69. 8 Operators Instead, you can flatten the Publisher using Type

    Erasure, and hide the implementation details of your chain publisher .compactMap { Float($0) } .removeDuplicates() .map { ceil($0) } .filter { $0 < 4 } .dropFirst() .append(stride(from: 0.3, to: 0.6, by: 0.1)) // Publishers.CompactMap<AnyPublisher<String, Never>, Float> // Publishers.RemoveDuplicates<Publishers.CompactMap<AnyPublisher<String… // Publishers.Map<Publishers.RemoveDuplicates<Publishers.CompactMap<AnyPubl… // Publishers.Filter<Publishers.Map<Publishers.RemoveDuplicates<Publishers.CompactM… // Publishers.Drop<Publishers.Filter<Publishers.Map<Publishers.RemoveDuplicates<Publishers.Co… .eraseToAnyPublisher() // AnyPublisher<Float, Never>
  70. 8 Operators One more operator you should know is print()

    As the name suggests, it prints any events passing through it Console Output: publisher // Emits “MobOS”, “Conf” .print("publisher") .map { $0.uppercased() } .print(“uppercased") publisher: receive subscription uppercased: receive subscription: (Print) uppercased: request unlimited publisher: request unlimited publisher: receive value: (MobOS) uppercased: receive value: (MOBOS) publisher: receive value: (Conf) uppercased: receive value: (CONF) publisher: receive finished uppercased: receive finished
  71. 8 Operators Since publishers unifies how work is represented, operators

    can be used on any kind of work you need to do map retry map retry Publisher<MyModel, Network.Error> Network Request Publisher<Data, VideoDecoding.Error> Video Decoding
  72. 9 Combine in Foundation Foundation provides some extremely useful publisher

    abstractions to wrap common constructs ["a", "b", "c"].publisher // "a", "b", "c" (1...5).publisher // 1, 2, 3, 4, 5 stride(from: 0, to: 100, by: 25).publisher // 0, 25, 50, 75 [“conf": "AppBuilders", "year": 2020].publisher // ("conf", "AppBuilders"), ("year": 2020) Sequence.publisher
  73. Foundation provides some extremely useful publisher abstractions to wrap common

    constructs NotificationCenter.publisher 9 Combine in Foundation
  74. 9 Combine in Foundation Hey daddy, can you tell me

    a joke? Sure son, no problem But with Combine, or i’ll cry!
  75. Foundation provides some extremely useful publisher abstractions to wrap common

    constructs URLSession.shared .dataTaskPublisher(for: request) // Publisher<(Data, URLResponse), URLError> URLSession.dataTaskPublisher 9 Combine in Foundation var request = URLRequest(url: URL(string: "https://icanhazdadjoke.com/")!) request.allHTTPHeaderFields = ["Accept": "text/plain"] .sink(receiveCompletion: { finished in switch finished { case .failure(let error): print("An error occurred! \(error)") case .finished: print("Completed with no issues") } }, receiveValue: { data, response in print(String(data: data, encoding: .utf8) ?? Data()) // Can a kangaroo jump higher than the Empire State Building? // Of course. The Empire State Building can't jump. :; })
  76. Foundation provides some extremely useful publisher abstractions to wrap common

    constructs URLSession.shared .dataTaskPublisher(for: request) // Publisher<(Data, URLResponse), URLError> .map(\.data) // Publisher<Data, URLError> .decode(type: Joke.self, decoder: JSONDecoder()) .sink(receiveCompletion: { finished in // Handle completion event }, receiveValue: { joke in print(joke) // Joke(id: 1337, // joke: “I used to work in a shoe recycling shop. // It was sole destroying. <”) }) URLSession.dataTaskPublisher 9 Combine in Foundation var request = URLRequest(url: URL(string: "https://icanhazdadjoke.com/")!) request.allHTTPHeaderFields = ["Accept": “application/json"] .decode(type: Joke.self, decoder: JSONDecoder())
  77. Foundation provides some extremely useful publisher abstractions to wrap common

    constructs scrollView.publisher(for: \.contentOffset) // Publisher<CGPoint, Never> avPlayer.publisher(for: \.status) // Publisher<AVPlayer.Status, Never> operation.publisher(for: \.queuePriority) // Publisher<Operation.QueuePriority, Never> Any KVO-compliant Key Paths 9 Combine in Foundation
  78. And one more treat masked as a property wrapper, @Published

    makes any variable a publisher @Published 9 Combine in Foundation $isLoading .map(!) .sink(receiveValue: { print("The opposite of loading is \($0)") }) @Published var isLoading: Bool = false isLoading // current value, accessed imperatively $isLoading // Publisher<Bool, Never> isLoading = true // Mutating notifies any subscribers
  79. Combine & SwiftUI Combine’s capabilities are deeply rooted within SwiftUI,

    usually masked under property wrappers ObservableObject !
  80. Combine & SwiftUI Combine’s capabilities are deeply rooted within SwiftUI,

    usually masked under property wrappers @ObservedObject !
  81. Combine & SwiftUI Combine’s capabilities are deeply rooted within SwiftUI,

    usually masked under property wrappers @EnvironmentObject !
  82. Combine & SwiftUI Combine’s capabilities are deeply rooted within SwiftUI,

    usually masked under property wrappers View.onReceive(_:perform:) !
  83. > Community = Combine conceptually builds upon many existing projects

    and the Reactive Streams spec, along with ones in the Swift ecosystem, for example RxSwift and ReactiveSwift. Luckily, much of your knowledge in either of them is applicable to Combine !
  84. iOS Deployment Target iOS 8+ iOS 13+ Supported Platforms iOS,

    macOS, tvOS, watchOS, Linux iOS, macOS, tvOS, watchOS Maintained By Open Source Apple UI Bindings RxCocoa, ReactiveCocoa SwiftUI (kinda) assign (Not specific to UIKit), no publishers Error model ReactiveSwift: Typed Errors RxSwift: No Typed Errors Typed Errors Backpressure No Yes > Community = ! Combine vs. existing projects
  85. Open-source Combine projects > Community = CombineCommunity/CombineCocoa UIKit bindings and

    extensions for Combine CombineCommunity/CombineDataSources Table and Collection data sources for Combine CombineCommunity/CombineExt Useful extensions Combine is missing ? sergdort/CombineFeedback Feedback-loop styled architecture based on Combine A pointfreeco/swift-composable-architecture Combine-based advanced architecture @ CombineCommunity/RxCombine Bi-directional type bridges between Combine and RxSwift
  86. Open-source Combine projects > Community = CombineCommunity/CombineCocoa UIKit bindings and

    extensions for Combine CombineCommunity/CombineDataSources Table and Collection data sources for Combine CombineCommunity/CombineExt Useful extensions Combine is missing ? sergdort/CombineFeedback Feedback-loop styled architecture based on Combine A pointfreeco/swift-composable-architecture Combine-based advanced architecture @ CombineCommunity/RxCombine Bi-directional type bridges between Combine and RxSwift https://combine.community
  87. Should I use Combine? / Is this a brand new

    iOS 13 and up project? Yes, Combine makes sense for your scenario! (especially if using SwiftUI) Is this an existing codebase ? Have some existing RxSwift code? Use something like RxCombine to migrate No existing Reactive code As long as you support iOS 13 and above Like anything, it depends…