SwiftUI and Combine – Binding, State, and notification of changes

When I started the project that became Using Combine, it was right after WWDC; I watched streamed WWDC sessions online, captivated like so many others about SwiftUI. I picked up this idea that SwiftUI was “built using the new framework: Combine”. In my head, I thought that meant Combine managed all the data – notifications and content – for SwiftUI. And well, that ain’t so. While the original “built using Combine” is accurate, it misses a lot of detail, and the truth is a bit more complex.

After I finished my first run through drafting the content for Using Combine, I took some time to dig back into SwiftUI. I originally intended to write (and learn) more about that. In fact, SwiftUI is what started the whole segue into Combine. I hadn’t really tried to use SwiftUI seriously, or get into the details until just recently. I realized after all the work on examples for Combine and UIKit, I had completely short shifted the SwiftUI examples.

Mirroring a common web technology pattern, SwiftUI works as a declarative structure of what gets shown with the detail being completely derived from some source of truth – derived from state stored or managed somewhere. The introductory docs made is clear that @State was how this declarative mechanism could represent a bit of local state within a View, and with the benefit of Daniel and Paul’s writing (SwiftUI Kickstart and SwiftUI by Example), it was also quickly clear that @EnvironmentObject and @ObservedObject played a role there too.

The Combine link to SwiftUI, as it turns out, is really only about notifying the SwiftUI components that a model had changed, not at all what changed. The key is the protocol from Combine: ObservableObject (Apple’s docs). This protocol, along with the @Published property wrapper, does the wonderful work of generating a combine publisher – the default type of which is represented by the class ObservableObjectPublisher. In the world of Combine, it has a defined output and failure type: <Void, Never>. The heart of that Void output type is that the data that is changing doesn’t matter – only that a change was happening.

So how does SwiftUI go and get the data it needs?Binding is the SwiftUI generic structure that is used to do this linkage. The documentation at Apple asserts:

Use a binding to create a two-way connection between a view and its underlying model. For example, you can create a binding between a Toggle and a Bool property of a State. Interacting with the toggle control changes the value of the Bool, and mutating the value of the Bool causes the toggle to update its presented state.

You can get a binding from a State by accessing its binding property. You can also use the $prefix operator with any property of a State to create a binding.

https://developer.apple.com/documentation/swiftui/binding

Looking around a bit more while creating some examples, and it becomes clear that some handy form elements (such as TextField) expect a parameter of type binding when they are declared. Binding itself works by leveraging swift’s property getters and setters. You can even manually create a Binding if you’re so inclined, defining the closures for get and set to whatever you like. Property wrappers such as @State, @ObservedObject, and @EnvironmentObject either create and expose a Binding, or create a wrapper that in turn passes back a Binding.

My take away is the flow with Combine and SwiftUI has a generally expected pattern: A model to be represented by a reference object, which sends updates when the data is about to change (by conforming to the ObservableObject protocol). SwiftUI goes and gets the data that it needs based on what was declared in the View using Binding to get to the underlying data (and potentially allowing the SwiftUI view to update it in turn if that’s relevant).

Given that SwiftUI views are also designed to be composed, I am leaning towards expecting a pattern that state will need to be defined for pretty much any variation of a view – and potentially externalized. The property wrappers for representing, and externalizing, state within SwiftUI are:

  • @State
  • @ObservedObject and @Published
  • @EnvironmentObject

@State is all about local representation, and the simplest mechanism, simply providing a link to a property and the Binding.

@ObservedObject (along with @Published) adds a notification mechanism on change, as well as a way to get a typed Binding to properties on the model. SwiftUI’s mechanism expects this always to be a reference type (aka a ‘class’), which ends up being pretty easy to define in code.

@EnvironmentObject takes that a step further and exposes a reference model not just to a single view, but allows it to be used by any number of views in their own hierarchy.

  • Drive most of the visual design choices entirely by the current state

But that’s not the only mechanism that is available: SwiftUI is also set up to react to a Combine publisher – although not in a heavily predetermined fashion. An interesting aspect is that all of the SwiftUI views also support a Combine subscriber: onReceive. So you can bring the publisher, and then write code within a View (or View component) to react to what it sends.

The onReceive subscriber acts very similarly to Combine’s sink subscriber – the single-closure version of sink (implying a Combine pipeline failure type of Never). You to define a closure within your SwiftUI view element that accepts that data and does whatever needs doing. This could be using the data, transforming and storing it into local @State, or just reacting to the fact that data was sent and updating the view based on that.

From a “What is a best practice” point of view, it seems the more you represent what you want to display within a reference model, the easier it will be to use. While you can expose a publisher right into a SwiftUI view, it tightly couples the combine publisher to the view and all links all those relevant types. You could (likely just as easily) have the model object encapsulate that detail – in which case the declaration of how you handle event changes over time are separated from how you present the view. This is likely a better separation of concerns.

The project (SwiftUI-Notes) linked to Using Combine now has two examples with Combine and SwiftUI. The first is a simple form validation (the view ReactiveForm.swift and model ReactiveFormModel.swift). This uses both the pattern of encapsulating the state within the model, and exposing a publisher to the SwiftUI View to show what can be done. I’m not espousing that the publisher mechanism is a good way to solve that particular problem, but it illustrates what can be done nicely.

The second example is a view (HeadingView.swift) that uses a model and publisher I created to use the built-in CoreLocation framework. The model (LocationModelProxy.swift) exposes the authorization as a published property, as well as the location updates through a publisher. Within the built-in Cocoa framework, those are normally exposed through a delegate callback. A large number of the existing Cocoa frameworks are convertible into a publisher-based mechanism to work with Combine using this pattern. The interesting bit was linking this up to SwiftUI, which was fun – although this example only taps the barest possibility of what could done.

It will be interesting to see what Apple might provide in terms of adopting Combine as alternative interfaces to its existing frameworks. CoreLocation is such a natural choice with its streaming updates, but there are a lot of others that could be used as well. And of course I’m looking forward to seeing how they expand on SwiftUI – and if they bring in more Combine based mechanisms into it or not.

Using Combine – reference content complete!

I’m thrilled to be announcing that an updated version of Using Combine is now available!

It has taken me nearly 6 months to draft it all, reverse engineering and writing tests for all the various publishers, operators, and pieces in between – and documenting what I found. The end result is 182 pages (in US PDF format) of reference documentation the way I’d generally like to have it.

While the live site is updated automatically, updated PDF and ePub versions are now available on Gumroad. If you purchased a copy previously, you can go to Gumroad and get an updated, DRM free, content in either PDF or ePub formats.

This updates finishes the largest swath of reference updates, creating tests to verify all the various operators and writing the documentation reference sections for the following:

There was also an update for Xcode 11.3 and associated iOS 13.3 and macOS 10.15.2, which included some subtle changes to the throttle operator behavior, which I recently wrote about in some detail.

With this update, the majority of the core content is now complete, but the work is by no means finished!

The next steps for the book are review and editing. On the to-do list are refining the descriptions of the reference sections, reviewing all the patterns now that we have had Combine for a few months, and seeing the updates as the API changes and refines. There are some diagrams now, but more are likely needed in some sections – both in the patterns and reference sections.

As before, this continues as a labor of love and for the community. Meaning that the content will continue to be free, available on the live site, with updates being made available as I make them. The work has been financially supported by 116 people as I’m writing this, as well as a number of people providing pull requests to fix typos and grammar flaws.

If you want a DRM-free digital copy in either PDF or ePub format, please consider supporting this work by purchasing a copy at gumroad.

Combine: throttle and debounce

Combine was announced and released this past summer with iOS 13. And with this recent iOS 13 update, it is still definitely settling into place. While writing Using Combine, I wrote a number of tests to verify and generally double-check my understanding of how Combine was working. With the update to iOS 13.3, the tests showed me that a few behaviors changed once again.

The operator that changed and trigged my tests was throttle. Throttle is meant to act on values being received from an upstream publisher over a sliding window. It collapses the values flowing through a pipeline over time, choosing a representative value from the set that appeared within a given time window. It also turns out that throttle has slightly different behavior when you’re working with a publisher that starts out sending down an initial value (such as a @Published property).

While I was poking at throttle to understand how it changed, I also realized that debounce was acting differently than I had originally understood, so I took some time to write some additional tests and make a more explicit timing diagram for it as well. Since debounce didn’t change behavior between 13.2 and 13.3 (that I spotted anyway), I’ll describe it first.

debounce

When you set up a debounce operator, you specify a time-window for it to react within. The operator collects all values that come in from the publisher, and most notably it resets the starting point for that sliding time window when it receives a value. It won’t send any values on until that entire window has expired without any other values appearing. In effect, it’s waiting for the value to settle. The marble diagrams show this really well.

Without a break that lets the sliding window expire, a single value is returned.
With a break that lets the sliding window expire, two values are propagated.

throttle

Throttle acts similarly to debounce, in that it collects multiple results over time and sends out a single result – but it does so with fixed time windows. Where debounce will reset the start of that window, throttle does not – so it doesn’t collapse the values entirely, but sort of “slows them down” (and that matches the name of the operator pretty well).

Which value from the set that arrive that’s chosen to be propagated is influenced by the parameter latest, which is set when you create the operator. In general, latest being set to true results in the last value appearing getting chosen, and latest being set to false results in the first value that appears. This is one of those items that is a lot easier to understand by looking at a marble diagram:

Throttle (latest=false), under iOS 13.2.2
throttle (latest=true) under iOS 13.2.2

The notable behavior change is how it handles initial values. Initial value seems to be a bit “flexible” in what is specifically initial though. When I first wrote my tests, I was using a class with a @Published variable, which sends a value upon subscription, and then updates when it is changed. To illustrate the 13.3 behavior change better, I re-wrote and expanded those tests to use a PassthroughSubject, so there wasn’t an automatic initial value.

throttle (latest=false) under iOS 13.3
throttle (latest=true) under iOS 13.3

So under iOS 13.3, the initial value (which is sent out roughly 100ms after the creation of the pipeline in my test), is always propagated, and then the sliding window effect begins immediately after that.

If you want to see the underlying tests that illustrate this, check out the following bits of code from the Using Combine project:

Using Combine (v0.8) update available!

A new version of Using Combine (v0.8) is now available.

The live HTML site for Using Combine is updated automatically, and the PDF and ePub versions are now available on Gumroad.

This version has a number of additional notes and changes, primarily from reader feedback, and some references to Combine’s changes with the release of IOS 13.2. A few more issues have been noted in the book, along with references to feedback reports sent to Apple where they may represent bugs or unexpected behavior in the current implementation.

This release also includes SVG based diagrams – so the original ASCII art diagrams are now gone, which should make that content far more accessible in the ePub format.

In addition, I added a section on marble diagrams, specifically in how to read them and how they apply to the code and examples illustrated in this book. I originally planned on generating all the marble diagrams, but after repeated efforts at that I backed off that idea and am creating them by hand, with the help of OmniGroup‘s wonderful tool OmniGraffle. And yes, the source for this is also in the github repository.

For the next release, I am planning on getting back to detailing out the as-yet-unwritten section on a number of operators. 

The project board at https://github.com/heckj/swiftui-notes/projects/1 also reflects all the various updates still remaining to be written.

A huge thank you to all who have supported this book and my efforts to provide it!

It is OK to test the framework

When I started to write the book Using Combine, I was learning the Combine framework as I went. There was a lot I was unsure about, and especially given that it was released with the beta of the operating system, the implementation was changing between beta releases as it firmed up. I chose to use a technique that I picked up years ago from Mike Clark – write unit tests against the framework to verify my understanding of it – while writing the book. (yes, I’m still working on it – it’s a very lengthy process)

While listening to a few episodes of the Under the Radar podcast, I heard a number of references to the idea of “make sure you’re not testing the framework”. It is generally good advice, in the vein of “make sure you’re testing your code first and foremost”, but as a snippet out of context and taken as a rule – I think it’s faulty. Don’t confuse what you are testing, but reliably testing underlying frameworks or libraries, especially while learning them or they evolve, can easily be worth the effort.

I have received a huge amount of value from testing frameworks – first in verifying that I understand what the library is doing and how it works. More over, it has been a very clear signal when regressions do happen, or intentional functionality changes.

If you do add tests of a framework or library into your codebase, I recommend you break them out into their own set of tests. If something does change in the library, it will be far more clear that it is a change from the library and not a cascading side effect in your code.

Most recently, this effort paid off when I stumbled across a regression in the Combine framework functionality with the GM release of Xcode 11.2. While I’ve been coming up to speed with the various operators, I’ve written unit tests that work the operators. In this case, the throttle operator – which has an option parameter latest – changed in how it operates with this release.

Throttle is very similar to the debounce operator, and in fact it operates the same if you use the option latest=true. They both take in values over time and return a single value for a specific time window. If you want the first value that’s sent within the timeframe, theoretically you should use latest=false with the throttle operator. This worked in earlier releases of Combine and Xcode – but in the latest release, it’s now disregarding that path and sending only the latest value.

You can see the tests I wrote to verify the functionality at https://github.com/heckj/swiftui-notes/blob/master/UsingCombineTests/DebounceAndRemoveDuplicatesPublisherTests.swift, and right now I’m working on a pull request to merge in the change reflecting the current release and illustrating the regression. And before you ask, yes – I have submitted this as a bug to Apple (FB7424221). If you are relying on the specific functionality of throttle with latest=false, be aware that the latest release of Xcode & Combine is likely going to mess with it.

If you are more curious about all the other tests that were created to support Using Combine, then feel free to check out the github repository heckj/swiftui-notes – the tests are in the UsingCombineTests directory, and set up as they’re own test target in the Xcode project. There are more to write, as I drive down into the various operators, so I do expect more will appear. I won’t assert that they’re all amazing, well constructed tests – but they’re getting the job done in terms of helping me understand how they work – and how they don’t work.

A Using Combine update now available!

A new version of Using Combine (v0.7) is now available! 

The free HTML site of Using Combine has been updated automatically, and the PDF and ePub versions are available on Gumroad.

This version has relatively few updates, primarily focused on some of the missing publishers and resolving the some of the egregious flaws in ePub rendering. No significant changes have come with the later Xcode and IOS betas, and with Xcode 11 now in GM release, it was a good time for another update to be made available.

For the next release, I am focusing on fleshing out a number of the not-yet-written reference sections on operators, most of which are more specialized than the more generally used ones that have already been covered.

The project board at https://github.com/heckj/swiftui-notes/projects/1 also reflects all the various updates still remaining to be written.

Using Combine (v0.6) available!

design by Michael Critz

A new version of Using Combine is available! The free/online version of Using Combine is updated automatically as I merge changes, and the PDF and ePub versions are released periodically and available on Gumroad.

https://gumroad.com/js/gumroad.js Purchase Using Combine

The book now has some amazing cover art, designed and provided by Michael Critz, and has benefited from updates provided by a number of people, now in the acknowledgements section.

The updates also include a section broken out focusing on developing with Combine, as well as a number of general improvements and corrections.

For the next release, I am going to focus on fleshing out a number of the not-yet-written reference sections:

the publishers I haven’t touched on yet

– starting into a number of the operators, most of which are more specialized

I reviewed the content prior to this release to see what was remaining to be done, and updated the project planning board with the various updates still remaining to be written.

I do expect we’ll see beta6 from Apple before too long, although exactly when is unknown. I thought it might appear last week, in which case I was planning on trying to accommodate any updates in this release. Xcode 11 beta6 hasn’t hit the streets and I wanted to get an update regardless of its inclusion.

navigating Swift Combine, tuples, and XCTest

What started out as a Github repository to poke at SwiftUI changed course a few weeks ago and became a documentation/book project on Combine, Apple’s provided framework for handling asynchronous event streams, not unlike ReactiveX. My latest writing project (available for free online at https://heckj.github.io/swiftui-notes/) has been a foil for me to really dig into and learn Combine, how it works, how to use it, etc. Apple’s beta documentation is unfortunately highly minimal.

One of the ways I’ve been working out how its all operating is writing a copious amount of unit tests, more or less “poking the beast” of the code and seeing how it’s operating. This has been quite successful, and I’ve submitted what I suspect are a couple of bugs to Apple’s janky FeedbackAssistant along with tests illustrating the results. As I’m doing the writing, I’m generating sample code and examples, and then often writing tests to help illuminate my understanding of how various Combine operators are functioning.

In the writing, I’ve worked my way through the sections to where I’m tackling some of the operators that merge streams of data. CombineLatest is where I started, and it testing it highlighted some of the more awkward (to me) pieces of testing swift code.

The heart of the issue revolves around asserting equality with XCTest, Apple’s unit testing framework, and the side effect that Combine takes advantage of tuples as lightweight types in operators like CombineLatest. In the test I created to validate how it was operating, I collected the results of the data into an ordered list. The originated streams had simple, equatable types – one String, the other Int. The resulting collection, however, was a tuple of <(String, Int)>.

To use XCTAssertEquals, the underlying types that you are validating need to conform to Equatable protocol. In the case of checking a collection type, it drops down and relies on the equatable conformance of its underlying type. And that is where it breaks down – tuples in swift aren’t allowed to conform to protocols – so they can’t declare (or implement conformance with) equatable.

I’m certainly not the first to hit this issue – there’s a StackOverflow question from 2016 that highlights the issue, Michael Tsai highlights the same on his blog (also from 2016). A slightly later, but very useful StackOverflow Q&A entitled XCTest’ing a tuple was super helpful, with nearly identical advice to Paul Hudson’s fantastic swift tips in Hacking With Swift: how to compare equality on tuples. I don’t know that my solution is a good one – it really feels like a bit of a hack, but I’m at least confident that it’s correct.

The actual collection of results that I’m testing is based on Tristan’s Combine helper library: Entwine and EntwineTest. Entwine provides a virtual time scheduler, allowing me to validate the timing of results from operators as well as the values themselves. I ended up writing a one-off function in the test itself that did two things:

  • It leveraged an idea I saw in how to test equality of Errors (which is also a pain in the tuckus) – by converting them to Strings using debugDescription or localizedDescription. This let me take the tuple and consistently dump it into a string format, which was much easier to compare.
  • Secondarily, I also wrote the function so that the resulting tests were easy to read in how they described the timing and the results that were expected for a relatively complex operator like combineLatest.

If you’re hitting something similar and want to see how I tackled it, the code is public UsingCombineTests/MergingPipelineTests.swift. No promises that this is the best way to solve the problem, but it’s getting the job done:

func testSequenceMatch(
    sequenceItem: (VirtualTime, Signal<(String, Int), Never>),
    time: VirtualTime,
    inputvalues: (String, Int)) -> Bool {

    if sequenceItem.0 != time {
        return false
    }
    if sequenceItem.1.debugDescription != Signal<(String, Int),
       Never>.input(inputvalues).debugDescription {
        return false
    }
    return true
}

XCTAssertTrue(
    testSequenceMatch(sequenceItem: outputSignals[0], 
                      time: 300, inputvalues: ("a", 1))
)

XCTAssertTrue(
    testSequenceMatch(sequenceItem: outputSignals[1], 
                      time: 400, inputvalues: ("b", 1))
)

Tristan, the author of Entwine and EntwineTest, provided me with some great insight into how this could be improved in the future. The heart of it being that while swift tuples don’t/can’t have conformance to protocols like Hashable and Equatable, structs within Swift do. It’s probably not sane to make every possible combinatorial set in your code, but it’s perfectly reasonable to make an interim struct, map the tuples into it, and then use that struct to do the testing.

Tristan also pointed out that a different struct would be needed for the arity of the tuple – for example, a tuple of <String, Int> and <String, String, Int> would need different structs. The example implementation that Tristan showed:

struct Tuple2 {
  let t0: T0
  let t1: T1
  init(_ tuple: (T0, T1)) {
    self.t0 = tuple.0
    self.t1 = tuple.1
  }
  var raw: (T0, T1) {
    (t0, t1)
  }
 }

extension Tuple2: Equatable where T0: Equatable, T1: Equatable {}
extension Tuple2: Hashable where T0: Hashable, T1: Hashable {}

A huge thank you to Tristan for taking the time to explain the tuple <-> struct game in swift!

After having spent quite a few years with dynamic languages, this feels like jumping through a lot of hoops to get the desired result, but I’m pleased that at least it also makes sense to me, so maybe I’m not entirely lost to the dynamic languages.