Combine
Swift, iOS 13.0+, macOS 10.15+
Overview
publisher : expose values that can change over time.
subscriber : receive those values from the publishers.
Protocol
Publisher
protocol Publisher
Publishers have operators to act on the values received from upstream publishers and republish them.
The publisher implements the receive(subscriber:)
method to accept a subscriber.
After this, the publisher can call the following methods on the subscriber :
receive(subscription:):
Acknowledges the subscribe request and returns aSubscription
instance. Thesubscriber
uses thesubscription
to demand elements from the publisher and can use it to cancel publishing.receive(_:):
Delivers one element from the publisher to the subscriber.receive(completion:):
Informs thesubscriber
that publishing has ended, either normally or with an error.
Subscriber
protocol Subscriber : CustomCombineIdentifierConvertible
Publishers only emit values when explicitly requested to do so by subscribers. This puts your subscriber code in control of how fast it receives events from the publishers it’s connected to.
Subscription
A protocol representing the connection of a subscriber to a publisher.
Declaration:
protocol Subscription : Cancellable, CustomCombineIdentifierConvertible
Topics:
request(_:)
, Tells a publisher that it may send more values to the subscriber.func request(_ demand: Subscribers.Demand)
CustomCombineIdentifierConvertible
A protocol for uniquely identifying publisher streams.
Inherited by: Subscriber
, Subscription
.
Declaration:
protocol CustomCombineIdentifierConvertible
Overview:
If you create a custom Subscription
or Subscriber
type, implement this protocol so that development tools can uniquely identify publisher chains in your app.
If your type is a class, Combine provides an implementation of
combineIdentifier
for you.If your type is a structure, set up the identifier as follows:
let combineIdentifier = CombineIdentifier()
Topics:
combineIdentifier
, A unique identifier for identifying publisher streams:var combineIdentifier: CombineIdentifier { get }
ObservableObject
A type of object with a publisher
that emits before the object has changed.
Declaration:
protocol ObservableObject : AnyObject
Overview:
By default an ObservableObject
synthesizes an objectWillChange
publisher that emits the changed value before any of its @Published
properties changes.
class Contact: ObservableObject {
@Published var name: String
@Published var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func haveBirthday() -> Int {
age += 1
return age
}
}
let john = Contact(name: "John Appleseed", age: 24)
cancellable = john.objectWillChange
.sink { _ in
print("\(john.age) will change")
}
print(john.haveBirthday())
// Prints "24 will change"
// Prints "25"
State
A property wrapper type that can read and write a value managed by SwiftUI.
Declaration:
@frozen @propertyWrapper struct State<Value>
Overview:
SwiftUI manages the storage of any property you declare as a state. When the state value changes, the view invalidates its appearance and recomputes the body
. Use the state as the single source of truth for a given view.
A State
instance isn’t the value itself; it’s a means of reading and writing the value. To access a state’s underlying value, use its variable name, which returns the wrappedValue
property value.
You should only access a state property from inside the view’s body, or from methods called by it. For this reason, declare your state properties as private
, to prevent clients of your view from accessing them. It is safe to mutate state properties from any thread.
To pass a state property to another view in the view hierarchy, use the variable name with the $
prefix operator. This retrieves a binding of the state property from its projectedValue
property.
For example, in the following code example PlayerView
passes its state property isPlaying
to PlayButton
using $isPlaying
.
Binding
A property wrapper type that can read and write a value owned by a source of truth.
Declaration:
@frozen @propertyWrapper @dynamicMemberLookup struct Binding<Value>
Overview:
Use a binding to create a two-way connection between a property that stores data, and a view that displays and changes the data. A binding connects a property to a source of truth stored elsewhere, instead of storing data directly.
For example, a button that toggles between play and pause can create a binding to a property of its parent view using the Binding
property wrapper.
struct PlayButton: View {
@Binding var isPlaying: Bool
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
HStack {
Image(systemName: (isPlaying ? "pause.circle" : "play.circle"))
Text("abc")
}
}
}
}
The parent view declares a property to hold the playing state, using the State
property wrapper to indicate that this property is the value’s source of truth.
struct PlayerView: View {
var name: String
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(name)
Text(isPlaying ? "✅" : "❌")
PlayButton(isPlaying: $isPlaying)
}
.padding()
}
}
When PlayerView
initializes PlayButton
, it passes a binding of its state property into the button’s binding property. Applying the $
prefix to a property wrapped value returns its projectedValue
, which for a state
property wrapper returns a binding to the value.
Whenever the user taps the PlayButton
, the PlayerView
updates its isPlaying
state.
StateObject
A property wrapper type that instantiates an observable object.
Declaration:
@frozen @propertyWrapper struct StateObject<ObjectType> where ObjectType : ObservableObject
Overview:
Create a state object in a View
, App
, or Scene
by applying the @StateObject
attribute to a property declaration and providing an initial value that conforms to the ObservableObject
protocol:
@StateObject var model = DataModel()
SwiftUI creates a new instance of the object only once for each instance of the structure that declares the object. When published
properties of the observable
object change, SwiftUI updates the parts of any view that depend on those properties:
Text(model.title) // Updates the view any time `title` changes.
You can pass the state object into a property that has the ObservedObject
attribute. You can alternatively add the object to the environment of a view hierarchy by applying the environmentObject(_:)
modifier:
ContentView()
.environmentObject(model)
If you create an environment object as shown in the code above, you can read the object inside ContentView
or any of its descendants using the EnvironmentObject
attribute:
@EnvironmentObject var model: DataModel
Get a Binding
to one of the state object’s properties using the $
operator. Use a binding when you want to create a two-way connection to one of the object’s properties. For example, you can let a Toggle
control a Boolean value called isEnabled
stored in the model:
Toggle("Enabled", isOn: $model.isEnabled)
ObservedObject
A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.
Declaration:
@propertyWrapper @frozen struct ObservedObject<ObjectType> where ObjectType : ObservableObject
Published
A type that publishes a property marked with an attribute.
Declaration:
@propertyWrapper struct Published<Value>
Overview:
Publishing a property with the @Published
attribute creates a publisher
of this type. You access the publisher with the $
operator, as shown here:
class Weather {
@Published var temperature: Double
init(temperature: Double) {
self.temperature = temperature
}
}
let weather = Weather(temperature: 20)
cancellable = weather.$temperature
.sink() {
print ("Temperature now: \($0)")
}
weather.temperature = 25
// Prints:
// Temperature now: 20.0
// Temperature now: 25.0
When the property changes, publishing occurs in the property’s willSet
block, meaning subscriber
s 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
@Published
attribute isclass
constrained. Use it with properties of classes, not with non-class types like structures.
EnvironmentObject
A property wrapper type for an observable
object supplied by a parent or ancestor view.
Declaration:
@frozen @propertyWrapper struct EnvironmentObject<ObjectType> where ObjectType : ObservableObject
Overview:
An environment object invalidates the current view whenever the observable object changes. If you declare a property as an environment object, be sure to set a corresponding model object on an ancestor view by calling its environmentObject(_:)
modifier.
EnvironmentValues
A collection of environment values propagated through a view hierarchy.
Declaration:
struct EnvironmentValues
Overview:
SwiftUI exposes a collection of values to your app’s views in an EnvironmentValues
structure. To read a value from the structure, declare a property using the Environment
property wrapper and specify the value’s key path. For example, you can read the current locale
:
@Environment(\.locale) var locale: Locale
Use the property you declare to dynamically control a view’s layout. SwiftUI automatically sets or updates many environment values, like pixelLength
, scenePhase
, or locale
, based on device characteristics, system state, or user settings. For others, like lineLimit, SwiftUI provides a reasonable default value.
You can set or override some values using the environment(_:_:)
view modifier:
MyView()
.environment(\.lineLimit, 2)
The value that you set affects the environment for the view that you modify — including its descendants in the view hierarchy — but only up to the point where you apply a different environment modifier.
SwiftUI provides dedicated view modifiers for setting some values, which typically makes your code easier to read. For example, rather than setting the lineLimit
value directly, as in the previous example, you should instead use the lineLimit(_:)
modifier:
MyView()
.lineLimit(2)
In some cases, using a dedicated view modifier provides additional functionality. For example, you must use the preferredColorScheme(_:)
modifier rather than setting colorScheme
directly to ensure that the new value propagates up to the presenting container when presenting a view like a popover:
MyView()
.popover(isPresented: $isPopped) {
PopoverContent()
.preferredColorScheme(.dark)
}
Create custom environment values by defining a type that conforms to the EnvironmentKey
protocol, and then extending the environment values structure with a new property. Use your key to get and set the value, and provide a dedicated modifier for clients to use when setting the value:
private struct MyEnvironmentKey: EnvironmentKey {
static let defaultValue: String = "Default value"
}
extension EnvironmentValues {
var myCustomValue: String {
get { self[MyEnvironmentKey.self] }
set { self[MyEnvironmentKey.self] = newValue }
}
}
extension View {
func myCustomValue(_ myCustomValue: String) -> some View {
environment(\.myCustomValue, myCustomValue)
}
}
Clients of your value then access the value in the usual way, reading it with the Environment
property wrapper, and setting it with the myCustomValue
view modifier.
Environment
A property wrapper that reads a value from a view’s environment.
Declaration:
@frozen @propertyWrapper struct Environment<Value>
Overview:
Use the Environment
property wrapper to read a value stored in a view’s environment. Indicate the value to read using an EnvironmentValues
key path in the property declaration. For example, you can create a property that reads the color scheme of the current view using the key path of the colorScheme
property:
@Environment(\.colorScheme) var colorScheme: ColorScheme
You can condition a view’s content on the associated value, which you read from the declared property’s wrappedValue
. As with any property wrapper, you access the wrapped value by directly referring to the property:
if colorScheme == .dark { // Checks the wrapped value.
DarkContent()
} else {
LightContent()
}
If the value changes, SwiftUI updates any parts of your view that depend on the value. For example, that might happen in the above example if the user changes the Appearance settings.
You can use this property wrapper to read — but not set — an environment value. SwiftUI updates some environment values automatically based on system settings and provides reasonable defaults for others. You can override some of these, as well as set custom environment values that you define, using the environment(_:_:)
view modifier.
For the complete list of environment values provided by SwiftUI, see the properties of the EnvironmentValues
structure. For information about creating custom environment values, see the EnvironmentKey
protocol.
WWDC Video
Last updated
Was this helpful?