Share
Embed
Getting Started with Combine
Getting Started with Combine, as presented by Shai Mishali in various conferences.
Shai Mishali
October 02, 2020
Transcript
111 slides
-
iOS Tech Lead @ Gett Open Source ❤ Hackathon fan
and winner of some International Speaker @freak4pc Ab t Me -
Involved in dozens of tutorials and several published books Author
& Editor at raywenderlich.com iOS Team @freak4pc Ab t Me -
“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?# $ -
“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?# $ -
(almost) Everything is Asynchronous Notification Center % KVO & Delegates
' Bindings ↔ UI Events ) Networking * Publisher ! -
(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! -
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 -
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! -
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> -
Publishers can emit 3 types of events Finished Failure (Failure)
Completion: Value (Output) Value: , Publisher -
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 -
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> -
- 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 -
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 ??? -
A subscription represents the connection between a publisher and a
subscriber and is where most of the “heavy work” happens . Subscription Publisher Subscriber Subscription -
The publishers provides a Subscription to the Subscriber via its
receive(subscription:) method . Subscription Publisher Subscriber receive(subscription:) Subscription -
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 -
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 -
/ A subscription also has one final extremely crucial role
in Apple’s Combine . Subscription -
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 -
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) -
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 -
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 -
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 -
⏳ 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. -
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 -
So now I have a bunch of Publishers. What do
I actually do with them? , Consuming Publishers #$ -
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 -
, 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 -
Getting values is useful, but don’t you wish you could
control and modify the values emitted by publishers? , Consuming Publishers ⁉ -
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 -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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” -
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 -
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 -
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 -
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” -
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” -
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 -
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” -
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” -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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… -
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 ' -
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 -
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> -
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 -
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 -
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 -
Foundation provides some extremely useful publisher abstractions to wrap common
constructs NotificationCenter.publisher 9 Combine in Foundation -
9 Combine in Foundation Hey daddy, can you tell me
a joke? Sure son, no problem But with Combine, or i’ll cry! -
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. :; }) -
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()) -
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 -
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 -
Combine & SwiftUI Combine’s capabilities are deeply rooted within SwiftUI,
usually masked under property wrappers ObservableObject ! -
Combine & SwiftUI Combine’s capabilities are deeply rooted within SwiftUI,
usually masked under property wrappers @ObservedObject ! -
Combine & SwiftUI Combine’s capabilities are deeply rooted within SwiftUI,
usually masked under property wrappers @EnvironmentObject ! -
Combine & SwiftUI Combine’s capabilities are deeply rooted within SwiftUI,
usually masked under property wrappers View.onReceive(_:perform:) ! -
> 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 ! -
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 -
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 -
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 -
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…