Swift UI and Combine
Original publish date: May, 2020SwiftUI and Combine are two new frameworks that were announced at WWDC 2019.
SwiftUI was introduced as
a revolutionary, new way to build better apps, faster.Combine is described as
a unified declarative framework for processing values over timeThese two frameworks received a lot of attention at WWDC 2019, as evidenced by the number of sessions in which these technologies were featured.
- 204 - Introducing SwiftUI: Building Your First App
- 210 - What's New in AppKit for MacOS
- 216 - SwiftUI Essentials
- 219 - SwiftUI on watchOS
- 226 - Data Flow Through SwiftUI
- 231 - Integrating SwiftUI
- 237 - Building Custom Views with SwiftUI
- 238 - Accessibility in SwiftUI
- 240 - SwiftUI On All Devices
- 402 - What’s New in Swift
- 415 - Modern Swift API Design
- 712 - Advances in Networking, Part 1
- 721 - Combine in Practice
- 722 - Introducing Combine
That's about 12 hours of video to watch. There are also a plethora of web sites with quick tutorials walking through how to get up and running using one or both of these frameworks. With all that information available, why am I writing a blog post on the subject?
Answer: my motivation is to simplify the existing information into something more compact and digestible. In particular, the tutorials often get down in the weeds, without tying together concepts at a higher level. In particular, much of the information focuses on either SwiftUI or Combine, but does not do a good job of describing how they work together. This blog post aims to address that short coming.
In SwiftUI, the declarative syntax is provided by the use of a Domain Specific Language (DSL). See this talk (around 34:00). Big picture is that declaring something such as a List in SwiftUI, is done using very high level statement, which is then translated by the framework into lower level instructions.
The screenshot and code below show an app I made in 20 seconds that uses Swift UI to create a List.
Background
SwiftUI
SwiftUI uses a declarative syntax. What does that mean? When you program declaratively, the instructions you write say "what" to do, but not "how". A more familiar example of declarative programming is HTML, which says for example: "this is a table". It is up to the browser how to display the table. As iOS developers, we have been introduced to the paradigm of declarative programming by using constraints in auto-layout; we give general instructions of what we want done, and leave it up to the system to figure out the details.In SwiftUI, the declarative syntax is provided by the use of a Domain Specific Language (DSL). See this talk (around 34:00). Big picture is that declaring something such as a List in SwiftUI, is done using very high level statement, which is then translated by the framework into lower level instructions.
The screenshot and code below show an app I made in 20 seconds that uses Swift UI to create a List.
Combine
Like SwiftUI, Combine is also a declarative framework. SwiftUI is a sub-type of declarative programming that uses a DSL. Combine is a different sub-type of declarative programming, namely functional programming. Wikipedia says that functional programmingis a declarative programming paradigm in which function definitions are trees of expressions that each return a value, rather than a sequence of imperative statements which change the state of the program or world.iOS developers may have been exposed to functional programming previously if they worked on hybrid mobile applications using Redux or Redux-Flow with React Native. Or iOS developers may have used third party Swift libraries for functional programming such as RxSwift.
Apple's Combine framework provides a unified (read: single, consistent) interface for accessing and combining existing asynchronous interfaces in cocoa touch. One example of an existing asynchronous interface is URLSession, which is not replaced by but now integrates with the Combine framework.
Combine defines two interfaces: Publisher and Subscriber. A publisher sends events to subscribers. See sequence diagram below.
Putting it all Together
The above descriptions of SwiftUI and Combine may not be enough to get you up and running with these new frameworks. It may take significant study, research, learning, and practice to become familiar with either SwiftUI or Combine, separately.But let's assume you've reached that point. Now you want to use Combine with SwiftUI. There does not seem to be an immediate connection between the two frameworks. Consider the Xcode screenshot and code below.
When previewing the app using the canvas, the image is loaded and displayed. There are just two classes to consider:
- ContentView -- the SwiftUI View subclass
- ActorViewModel -- the source of the data for the ContentView (called a ViewModel as it performs the role of VM in MVVM)
The view has a reference to the actor object, as per the class diagram below.
Although this example is using Combine, it is not immediately apparent. There is no mention of a Publisher or a Subscriber. What is going on?
Answer: Looking at the class hierarchy fills in the missing gaps. The below class diagram explains the full picture (click on the image to see it in greater detail).
Consulting Apple's documentation provides definitions for these types:
- ObservedObject
- A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.
- ObservableObject
- A type of object with a publisher that emits before the object has changed. By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its @Published properties changes.
- objectWillChange
- A publisher that emits before the object has changed.
- PassthroughSubject
- A subject that broadcasts elements to downstream subscribers. As a concrete implementation of Subject, the PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
First, consider what the "@ObservedObject" means. This is a property wrapper. A property wrapper reduces code duplication, and allows for a succinct syntax when declaring properties that hides how the property is stored and defined. In this case, the "Observed Object" is a property which observes another object.
In other words, the property is a Subscriber (from the Combine Framework). The actor is (through the use of a property wrapper) is a Subscriber, which subscribes to a Publisher, but what is the Publisher in this scenario?
The "Observable Object" is not itself the publisher, but rather has a publisher. The ActorViewModel conforms to the "ObservableObject" protocol. By doing so, it is provided with a publisher property called "objectWillChange" by an extension (which the framework provides on the ObservableObject protocol). This "objectWillChange" property is of type "PassthroughSubject", which is a concrete type of the Publisher protocol. The passthrough subject has a property called "send", which is a publisher method used to send data to any subscribers. So the property called "objectWillChange" is the Publisher.
To recap, the Subscriber is the property called "actor" from the ContentView class, and the Publisher is the property "objectWillChange" from the ActorViewModel class. What about the need for the Subscriber to Subscribe to the Publisher? The "@ObservedObject" property wrapper is itself a Subscriber, so it must subscribe to the Publisher. But how does the View find out about changes sent to the Subscriber? That is handled by the SwiftUI framework, which we never see. There is some behind-the-scenes magic here, some details of which are eluded to by the WWDC session "Data Flow Through Swift UI".
And you might wonder, how does this work? What is this additional behavior with state? When you (use) state the framework, allocate the persistent storage for the variable on the view we have and track it as a dependency because if the system is creating storage for you, you always have to specify an initial constant value.
View can be recreated often by the system, but with state, the framework knows that it needs to persist the storage across multiple update of the same view.
....
But I want to take you behind the scene and show you what's happening when the user is tapping on the button. Let's start with view hierarchy we just showed.
We just say that when-- that when we define some state, the framework is allocating persistent storage for you. One was the special property of state variable is that SwiftUI kind of start when they change. And because SwiftUI knows that the state variable was writing the body, it knows that the view rendering depend on that state variable.
....
This is exactly what we mentioned earlier when we say that the framework manages the dependency for you. But we also talk about source of truth before and you should remember that every time you declare a state, you define a new source of truth that is owned by your view. And while this is so important that I'm showing to you in big letter, another important takeaway is that view are a function of state, not a sequence of event. Traditionally, you respond to some event by directly mutating your view hierarchy. For example, by adding or removing a subview or changing the alpha. Instead, in SwiftUI, you mutate some state and the state work as a source of truth from which you derive your view. This is where SwiftUI, declarative syntax shine. You describe your view given the current state.
Take-away: we don't need to worry about subscribing the view to the Publisher. On the other hand, we do need to worry about making sure the publisher tell the subscriber when something is about to change. When the image has been fetched from a remote server, and the data has been transformed into an image object, we call "objectWillChange.send()" to inform the View. Once the subscriber receives notification from the publisher that something is about to / has changed, it invalidates the view (which results in the view redrawing itself).
Summary
As stated in the introduction, declarative programming is very high level and hides many of the implementation details. The way in which SwiftUI uses a ObservedObject PropertyWrapper does not on the surface give away the fact that Combine even exists in the equation. But by inspecting ObservedObject and ObservableObject, the underlying Combine framework is revealed, along with the design pattern ofsubscriber --> subscribing to a publisher --> which then publishes changes --> that are received by the subscriber
There are many other ways to connect Combine and SwiftUI; this article just looked at one possible method. For example, rather than use the "objectWillChange" property, the Observed Object could instead mark its properties as "@Published", using yet another kind of property wrapper.