Combine Asynchronous Programming

1 Combine basics

  • Combine is a declarative, reactive framework for processing asynchronous events over time.
  • It aims to solve existing problems, like unifying tools for asynchronous programming, dealing with mutable state and making error handling a starting team player.
  • Combine revolves around three main types: publishers to emit events over time, operators to asynchronously process and manipulate upstream events and subscribers to consume the results and do something useful with them.

The three key moving pieces in Combine are publishers, operators and subscribers

1.1 Publishers

Publishers are types that can emit values over time to one or more interested parties, such as subscribers. Regardless of the internal logic of the publisher, which can be pretty much anything including math calculations, networking or handling user events, every publisher can emit multiple events of these three types:
1. An output value of the publisher's generic Output type.
2. A successful completion.
3. A completion with an error of the publisher's Failure type.

Take a look at the Publisher protocol and one of its most crucial extensions:

public protocol Publisher {
  associatedtype Output
  associatedtype Failure : Error
  func receive<S>(subscriber: S) where S: Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
extension Publisher {
    public func subscribe<S>(_ subscriber: S) where S : Subscriber,
    Self.Failure == S.Failure,
    Self.Output == S.Input
}

The associated types are the publisher’s interface that a subscriber must match in order to create a subscription.

1.1.1 Primitive value type Publisher

  • Just, it’s a publisher that emits its output to each subscriber once and then finishes.
let just = Just("Hello world!")
_ = just.sink(
    receiveCompletion: {
        print("Received completion", $0)
    },
    receiveValue: {
        print("Received value", $0)
    })

1.2. Operators

Operators are methods declared on the Publisher protocol that return either the same or a new publisher. That's very useful because you can call a bunch of operators one after the other, effectively chaining them together.

1.2.1 sink(_:_:)

The sink(_:_:) operator allows you to provide closures with your code that will receive output values and completions. From there, you can do anything your heart desires with the received events.
sink(_:_:), it simply provides an easy way to attach a subscriber with closures to handle output from a publisher.

let just = Just("Hello world!")
_ = just .sink(
      receiveCompletion: {
        print("Received completion", $0)
      },
      receiveValue: {
        print("Received value", $0)
    })

1.2.2 assign(to:on:)

The assign(to:on:) operator allows you to, without the need of custom code, bind the resulting output to some property on your data model or on a UI control to display the data directly on-screen via a key path. assign(to:on:), the built-in assign(to:on:) operator enables you to assign the received value to a KVO-compliant property of an object.

1.3. Subscribers

Every subscription ends with a subscriber. Subscribers generally do "something" with the emitted output or completion events.

Subscriber protocol:

public protocol Subscriber: CustomCombineIdentifierConvertible {
    associatedtype Input
    associatedtype Failure: Error
    func receive(subscription: Subscription)
    func receive(_ input: Self.Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Self.Failure>) 
}

1.4. Subscription

The connection between the publisher and the subscriber is the subscription. Here’s the Subscription protocol:

public protocol Subscription: Cancellable, CustomCombineIdentifierConvertible {
    func request(_ demand: Subscribers.Demand) 
}

2. Custom subscriber

final class IntSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never
    func receive(subscription: Subscription) {
        subscription.request(.max(3)) 
    }
    func receive(_ input: Int) -> Subscribers.Demand { 
        print("Received value", input)
        return .none 
    }
    func receive(completion: Subscribers.Completion<Never>) {
        print("Received completion", completion)
    }
}

About Tang

A mobile developer, work in Stockholm, Sweden
This entry was posted in iOS, Mobile and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s