Publishers
For general information about publishers see Publishers and Lifecycle of Publishers and Subscribers.
enum Publishers
enum Publishers docs: Publishers
A namespace for types that serve as publishers.
Declaration:
Overview:
The various operators defined as extensions on Publisher implement their functionality as classes or structures that extend this enumeration. For example, the contains(_:) operator returns a Publishers.Contains instance.
Just
A publisher that emits an output to each subscriber just once, and then finishes.
Declaration:
Overview:
You can use a Just publisher to start a chain of publishers. A Just publisher is also useful when replacing a value with Publishers.Catch.
In contrast with
Result.Publisher, aJustpublisher can’t fail with an error.And unlike
Optional.Publisher, aJustpublisher always produces a value.
Just has a failure type of <Never>, often used within a closure to flatMap in error handling, it creates a single-response pipeline for use in error handling of continuous values.
Future
A publisher that eventually produces a single value and then finishes or fails.
Declaration:
Overview:
Future is a publisher that lets you combine in any asynchronous call and use that call to generate a value or a completion as a publisher. It is ideal for when you want to make a single request, or get a single response, where the API you are using has a completion handler closure.
The obvious example that everyone immediately thinks about is URLSession. Fortunately, URLSession.dataTaskPublisher exists to make a call with a URLSession and return a publisher.
If you already have an API object that wraps the direct calls to URLSession, then making a single request using Future can be a great way to integrate the result into a Combine pipeline.
There are a number of APIs in the Apple frameworks that use a completion closure. An example of one is requesting permission to access the contacts store in Contacts.
An example of wrapping that request for access into a publisher using Future might be:
1️⃣
Futureitself has you define the return types and takes a closure. It hands in aResultobject matching the type description, which you interact.2️⃣ You can invoke the async API however is relevant, including passing in its required closure.
3️⃣ Within the completion handler, you determine what would cause a failure or a success. A call to
promise(.failure(<FailureType>))returns the failure.4️⃣ Or a call to
promise(.success(<OutputType>))returns a value.
If you want to wrap an async API that could return many values over time, you should not use Future directly, as it only returns a single value. Instead, you should consider creating your own publisher based on passthroughSubject or currentValueSubject, or wrapping the Future publisher with Deferred.
Futurecreates and invokes its closure to do the asynchronous request at the time of creation, not when the publisher receives a demand request. This can be counter-intuitive, as many other publishers invoke their closures when they receive demand. This also means that you can’t directly link aFuturepublisher to an operator likeretry.The
retryoperator works by making another subscription to the publisher, andFuturedoesn’t currently re-invoke the closure you provide upon additional request demands. This means that chaining aretryoperator after Future will not result in Future’s closure being invoked repeatedly when a.failurecompletion is returned.The failure of the
retryandFutureto work together directly has been submitted to Apple as feedback: FB7455914.The
Futurepublisher can be wrapped withDeferredto have it work based on demand, rather than as a one-shot at the time of creation of the publisher. You can see unit tests illustratingFuturewrapped withDeferredin the tests at UsingCombineTests/FuturePublisherTests.swift.
If you are wanting repeated requests to a Future (for example, wanting to use a retry operator to retry failed requests), wrap the Future publisher with Deferred.
1️⃣ The closure provided in to
Deferredwill be invoked as demand requests come to the publisher.2️⃣ This in turn resolves the underlying api call to generate the result as a
Promise, with internal closures to resolve the promise.
Deferred
A publisher that awaits subscription before running the supplied closure to create a publisher for the new subscriber.
Declaration:
Overview:
Deferred is useful when creating an API to return a publisher, where creating the publisher is an expensive effort, either computationally or in the time it takes to set up. Deferred holds off on setting up any publisher data structures until a subscription is requested. This provides a means of deferring the setup of the publisher until it is actually needed.
The Deferred publisher is particularly useful with Future, which does not wait on demand to start the resolution of underlying (wrapped) asynchronous APIs.
Empty
A publisher that never publishes any values, and optionally finishes immediately.
Declaration:
Overview:
You can create a ”Never” publisher — one which never sends values and never finishes or fails — with the initializer Empty(completeImmediately: false).
Emptyis useful in error handling scenarios where the value is an optional, or where you want to resolve an error by simply not sending anything.Emptycan be invoked to be a publisher of any output and failure type combination.
Empty is most commonly used where you need to return a publisher, but don’t want to propagate any values (a possible error handling scenario). If you want a publisher that provides a single value, then look at Just or Deferred publishers as alternatives.
When subscribed to, an instance of the Empty publisher will not return any values (or errors) and will immediately return a finished completion message to the subscriber.
An example of using Empty:
Because the types are not be able to be inferred, expect to define the types you want to return.
Fail
A publisher that immediately terminates with the specified error.
Declaration:
Overview:
Failis commonly used when implementing an API that returns a publisher. In the case where you want to return an immediate failure,Failprovides a publisher that immediately triggers a failure on subscription. One way this might be used is to provide a failure response when invalid parameters are passed. TheFailpublisher lets you generate a publisher of the correct type that provides a failure completion when demand is requested.
Initializing a Fail publisher can be done two ways: with the type notation specifying the output and failure types or with the types implied by handing parameters to the initializer.
For example:
Initializing Fail by specifying the types:
Initializing Fail by providing types as parameters:
Record
A publisher that allows for recording a series of inputs and a completion, for later playback to each subscriber.
Declaration:
Overview:
Recordallows you to create a publisher with pre-recorded values for repeated playback.Recordacts very similarly toPublishers.Sequenceif you want to publish a sequence of values and then send a.finishedcompletion. It goes beyond that allowing you to specify a.failurecompletion to be sent from the recording.Recorddoes not allow you to control the timing of the values being returned, only the order and the eventual completion following them.
Record can also be serialized (encoded and decoded) as long as the output and failure values can be serialized as well.
An example of a simple recording that sends several string values and then a .finished completion:
The resulting instance can be used as a publisher immediately:
Record also has a property recording that can be inspected, with its own properties of output and completion. Record and recording do not conform to Equatable, so can’t be easily compared within tests. It is fairly easy to compare the properties of output or completion, which are Equatable if the underlying contents (output type and failure type) are equatable.
Note: No convenience methods exist for creating a recording as a subscriber. You can use the receive methods to create one, wrapping a
sinksubscriber.
Publishers.Sequence
A publisher that publishes a given sequence of elements.
Declaration:
Overview:
Sequenceprovides a way to return values as subscribers demand them initialized from a collection. Formally, it provides elements from any type conforming to theSequenceprotocol.
If a subscriber requests unlimited demand, all elements will be sent, and then a .finished completion will terminate the output. If the subscribe requests a single element at a time, then individual elements will be returned based on demand.
If the type within the sequence is denoted as optional, and a nil value is included within the sequence, that will be sent as an instance of the optional type.
Combine provides an extension onto the Sequence protocol so that anything that corresponds to it can act as a sequence publisher. It does so by making a .publisher property available, which implicitly creates a Publishers.Sequence publisher.
Publishers.MakeConnectable
A publisher that provides explicit connectability to another publisher.
Creates a or converts a publisher to one that explicitly conforms to the ConnectablePublisher protocol. The failure type of the publisher must be <Never>.
Declaration:
Overview:
Publishers.MakeConnectable is a ConnectablePublisher, which allows you to perform configuration before publishing any elements. Call connect() on this publisher when you want to attach to its upstream publisher and start producing elements. Use the makeConnectable() operator to wrap an upstream publisher with an instance of this publisher.
A connectable publisher has an explicit mechanism for enabling when a subscription and the flow of demand from subscribers will be allowed to the publisher. By conforming to the
ConnectablePublisherprotocol, a publisher will have two additional methods exposed for this control:connect()andautoconnect(). Both of these methods return aCancellable.
When using connect(), the receipt of subscription will be under imperative control. Normally when a subscriber is linked to a publisher, the connection is made automatically, subscriptions get sent, and demand gets negotiated per the Lifecycle of Publishers and Subscribers. With a connectable publisher, in addition to setting up the subscription connect() needs to be explicitly invoked. Until connect() is invoked, the subscription won’t be received by the publisher.
The above code will not activate the subscription, and in turn show any results. In order to enable the subscription, an explicit connect() is required:
One of the primary uses of having a connectable publisher is to coordinate the timing of connecting multiple subscribers with multicast. Because multicast only shares existing events and does not replay anything, a subscription joining late could miss some data. By explicitly enabling the connect(), all subscribers can be attached before any upstream processing begins.
In comparison, autoconnect() makes a Connectable publisher act like a non-connectable one. When you enabled autoconnect() on a Connectable publisher, it will automate the connection such that the first subscription will activate upstream publishers.
1️⃣
makeConnectable()wraps an existing publisher and makes it explicitly connectable.2️⃣
autoconnect()automates the process of establishing the connection for you; The first subscriber will establish the connection, subscriptions will be forwards and demand negotiated.
Info: Making a publisher connectable and then immediately enabling
autoconnectis an odd example, as you typically want one explicit pattern of behavior or the other. The two mechanisms allow you to choose which you want for the needs of your code. As such, it is extremely unlikely that you would ever want to usemakeConnectable()followed immediately byautoconnect().
Both Timer and multicast are examples of connectable publishers.
SwiftUI
The SwiftUI framework is based upon displaying views from explicit state; as the state changes, the view updates.
SwiftUI uses a variety of property wrappers within its Views to reference and display content from outside of those views. @ObservedObject, @EnvironmentObject, and @Published are the most common that relate to Combine. SwiftUI uses these property wrappers to create a publisher that will inform SwiftUI when those models have changed, creating a objectWillChange publisher. Having an object conform to ObservableObject will also get a default objectWillChange publisher.
SwiftUI uses ObservableObject, which has a default concrete class implementation called ObservableObjectPublisher that exposes a publisher for reference objects (classes) marked with @ObservedObject.
Binding
SwiftUI does this primarily by tracking the state and changes to the state using the SwiftUI struct Binding. A binding is not a Combine pipeline, or even usable as one. A Binding is based on closures that are used when you get or set data through the binding. When creating a Binding, you can specify the closures, or use the defaults, which handles the needs of SwiftUI elements to react when data is set or request data when a view requires it.
There are a number of SwiftUI property wrappers that create bindings:
@State: creates a binding to a local view property, and is intended to be used only in one view.
when you create:
then: exampleString is the state itself and the property wrapper creates $exampleString (also known as property wrapper’s projected value) which is of type Binding<String>.
@Binding: is used to reference an externally provided binding that the view wants to use to present itself. You will see there upon occasion when a view is expected to be component, and it is watching for its relevant state data from an enclosing view.@EnvironmentObject: make state visible and usable across a set of views.@EnvironmentObjectis used to inject your own objects or state models into the environment, making them available to be used by any of the views within the current view hierarchy.
Info: The exception to
@EnvironmentObjectcascading across the view hierarchy in SwiftUI is notably when using sheets. Sheets don’t inherit the environment from the view through which they are presented.
@Environment isused to expose environmental information already available from within the frameworks, for example:
SwiftUI and Combine
All of this detail on Binding is important to how SwiftUI works, but irrelevant to Combine - Bindings are not combine pipelines or structures, and the classes and structs that SwiftUI uses are directly transformable from Combine publishers or subscribers.
SwiftUI does, however, use combine in coordination with Bindings. Combine fits in to SwiftUI when the state has been externalized into a reference to a model object, most often using the property wrappers @ObservedObject to reference a class conforming to the ObservableObject protocol.
The core of the ObservableObject protocol is a combine publisher objectWillChange, which is used by the SwiftUI framework to know when it needs to invalidate a view based on a model changing. The objectWillChange publisher only provides an indicator that something has changed on the model, not which property, or what changed about it.
The author of the model class can "opt-in" properties into triggering that change using the @Published property wrapper. If a model has properties that aren’t wrapped with @Published, then the automatic objectWillChange notification won’t get triggered when those values are modified. Typically the model properties will be referenced directly within the View elements. When the view is invalidated by a value being published through the objectWillChange publisher, the SwiftUI View will request the data it needs, as it needs it, directly from the various model references.
The other way that Combine fits into SwiftUI is the method onReceive, which is a generic instance method on SwiftUI views.
onReceive can be used when a view needs to be updated based on some external event that isn’t directly reflected in a model’s state being updated.
While there is no explicit guidance from Apple on how to use onReceive vs. models,
as a general guideline it will be a cleaner pattern to update the model using Combine, keeping the combine publishers and pipelines external to SwiftUI views. In this mode, you would generally let the
@ObservedObjectSwiftUI declaration automatically invalidate and update the view, which separates the model updating from the presentation of the view itself.The alternative ends up having the view bound fairly tightly to the combine publishers providing asynchronous updates, rather than a coherent view of the end state. There are still some edge cases and needs where you want to trigger a view update directly from a publishers output, and that is where
onReceiveis most effectively used.
ObservableObject
A type of object with a publisher that emits before the object has changed.
Declaration:
Overview:
By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its @Published properties changes.
When a class includes a
@Publishedproperty and conforms to theObservableObjectprotocol, this class instances will get aobjectWillChangepublisher endpoint providing this publisher. TheobjectWillChangepublisher will not return any of the changed data, only an indicator that the referenced object has changed.
The output type of ObservableObject.Output is type aliased to Void, so while it is not nil, it will not provide any meaningful data. Because the output type does not include what changes on the referenced object, the best method for responding to changes is probably best done using sink.
In practice, this method is most frequently used by the SwiftUI framework. SwiftUI views use the @ObservedObject property wrapper to know when to invalidate and refresh views that reference classes implementing ObservableObject.
Classes implementing ObservableObject are also expected to use @Published to provide notifications of changes on specific properties, or to optionally provide a custom announcement that indicates the object has changed.
It can also be used locally to watch for updates to a reference-type model.
@Published
A property wrapper that adds a Combine publisher to any property.
Declaration:
Overview:
Publishing a property with the @Published attribute creates a publisher of this type. You access the publisher with the $ operator, as shown here:
When the property changes, publishing occurs in the property’s willSet block, meaning subscribers receive the new value before it’s actually set on the property. In the above example, the second time the sink executes its closure, it receives the parameter value 25. However, if the closure evaluated weather.temperature, the value returned would be 20.
Important: The
@Publishedattribute is class constrained. Use it with properties of classes, not with non-class types like structures.
@Published is part of Combine, but allows you to wrap a property, enabling you to get a publisher that triggers data updates whenever the property is changed. The publisher’s output type is inferred from the type of the property, and the error type of the provided publisher is <Never>.
A smaller examples of how it can be used:
1️⃣
@Publishedwraps the property,username, and will generate events whenever the property is changed. If there is a subscriber at initialization time, the subscriber will also receive the initial value being set. The publisher for the property is available at the same scope, and with the same permissions, as the property itself.2️⃣ The publisher is accessible as
$username, of typePublished<String>.publisher.3️⃣ A Published property can have more than one subscriber pipeline triggering from it.
4️⃣ If you are publishing your own type, you may find it convenient to publish an array of that type as the property, even if you only reference a single value. This allows you represent an "Empty" result that is still a concrete result within Combine pipelines, as
assignandsinksubscribers will only trigger updates on non-nilvalues.
If the publisher generated from @Published receives a cancellation from any subscriber, it is expected to, and will cease, reporting property changes. Because of this expectation, it is common to arrange pipelines from these publishers that have an error type of <Never> and do all error handling within the pipelines.
For example, if a sink subscriber is set up to capture errors from a pipeline originating from a @Published property, when the error is received, the sink will send a cancel message, causing the publisher to cease generating any updates on change. This is illustrated in the test testPublishedSinkWithError at UsingCombineTests/PublisherTests.swift.
Additional examples of how to arrange error handling for a continuous publisher like @Published can be found at Using flatMap with catch to handle errors.
Foundation
NotificationCenter
Foundation’s NotificationCenter added the capability to act as a publisher, providing Notifications to pipelines.
NotificationCenter provides a publisher upon which you may create pipelines to declaratively react to application or system notifications. The publisher optionally takes an object reference which further filters notifications to those provided by the specific reference.
A number of AppKit controls provide notifications when the control has been updated. For example, AppKit’s TextField triggers a number of notifications including:
textDidBeginEditingNotificationtextDidChangeNotificationtextDidEndEditingNotification
1️⃣ Notifications are defined by a string for their name. If defining your own, be careful to define the strings uniquely.
2️⃣ A
NotificationCenterpublisher can be created for a single type of notification,.yourNotificationin this case, defined previously in your code.3️⃣ Notifications are received from the publisher. These include at least their
name, and optionally aobjectreference from the sending object - most commonly provided from Apple frameworks. Notifications may also include auserInfodictionary of arbitrary values, which can be used to pass additional information within your application.
Timer
Foundation’s Timer added the capability to act as a publisher, providing a publisher to repeatedly send values to pipelines based on a Timer instance.
Timer.publishreturns an instance ofTimer.TimerPublisher. This publisher is a connectable publisher, conforming toConnectablePublisher. This means that even when subscribers are connected to it, it will not start producing values untilconnect()orautoconnect()is invoked on the publisher.
Creating the timer publisher requires an interval in seconds, and a RunLoop and mode upon which to run. The publisher may optionally take an additional parameter tolerance, which defines a variance allowed in the generation of timed events. The default for tolerance is nil, allowing any variance.
The publisher has an output type of Date and a failure type of <Never>.
If you want the publisher to automatically connect and start receiving values as soon as subscribers are connected and make requests for values, then you may include autoconnect() in the pipeline to have it automatically start to generate values as soon as a subscriber requests data.
Alternatively, you can connect up the subscribers, which will receive no values until you invoke connect() on the publisher, which also returns a Cancellable reference.
publisher from a KeyValueObserving instance
Foundation added the ability to get a publisher on any NSObject that can be watched with Key Value Observing. To create this publisher, you call the function publisher on the object, providing it with a single (required) KeyPath value.
For example:
Relying on the interface element’s state to trigger updates into pipelines can lead to your state being very tightly bound to the interface elements, rather than your model. You may be better served by explicitly creating your own state to react to from a
@Publishedproperty wrapper.
URLSession.dataTaskPublisher
Foundation’s URLSession has a publisher specifically for requesting data from URLs: dataTaskPublisher
dataTaskPublisher, on URLSession, has two variants for creating a publisher. The first takes an instance of URL, the second URLRequest. The data returned from the publisher is a tuple of (data: Data, response: URLResponse).
Result
Foundation also adds Result as a publisher.
Combine augments Result from the swift standard library with a .publisher property, returning a publisher with an output type of Success and a failure type of Failure, defined by the Result instance.
Any method that returns an instance of Result can use this property to get a publisher that will provide the resulting value and followed by a .finished completion, or a .failure completion with the relevant Error.
Last updated