By Casey Liss
Quick Notes on Yesterday's Post

Within just a couple hours of posting yesterday, I had some new information to consider. I wanted to call attention to it before continuing on our RxSwift/Combine comparison.

New Documentation

Yesterday afternoon Apple released iOS 13 beta 2; with it came some new documentation. This new document, Receiving and Handling Events with Combine, is a brief overview of how one can, well, receive and handle events in Combine.

The introduction is good, and demonstrates how one can get a value out of a text field and store it in a custom model object. The documentation also demonstrates the use of operators to do some slightly more advanced modification of the stream in question.

Sample Code

Cutting to the end of the document, here’s the sample code Apple shared:

let sub = NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)
    .map( { ($0.object as! NSTextField).stringValue } )
    .assign(to: \MyViewModel.filterString, on: myViewModel)

I have… a lot of problems with this.

I’m Notifying You I Don’t Like This

Most of my problems with this code are in the first two lines:

let sub = NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)

NotificationCenter is a sort of application (or even system) bus, where lots of things can all drop data, or pick up pieces of data that are flying by. It’s a sort of all-things-to-all-people kind of solution, and that’s by design. There are lots of instances where you may want to be able to figure out if, say, the keyboard has just been shown or hidden. NotificationCenter is a great way to spread that message around within the system.

I find NotificationCenter to be a bit of a code smell. There are absolutely times where I use NotificationCenter, and in fact, there are times [like the keyboard notification above] that NotificationCenter is the best possible solution for a problem. However, all too often I feel like using NotificationCenter is the most convenient solution.

It’s extremely easy to drop something on the NotificationCenter bus, and to pick it up somewhere else on the other side of your app.

Furthermore, NotificationCenter is “stringly” typed, which is to say, it’s easy to make errors about what notification you’re trying to post or listen for. Swift does its best to make this a bit better, but ultimately it’s still NSString under the hood.

An Aside about Key-Value Observation

A popular way to get notifications about things changing in different pieces of code is a technology that has been around for a long time in Apple platforms: key-value observation. Key-value observation is described by Apple as such:

Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.

I also noticed, thanks to a tweet from Gui Rambo, that Apple has added bindings for KVO to Combine in this new beta. That means that a lot of my gripes about there being no equivalent to RxCocoa in Combine may have gone away. If I could use KVO, that would probably obviate much of the need for “CombineCocoa”, so to speak.

I got to working on a sample of my own that used KVO to get the value out of a UITextField and simply print() it to the console:

let sub = self.textField.publisher(for: \UITextField.text)
    .sink(receiveCompletion: { _ in
        print("Completed")
    }, receiveValue: {
        print("Text field is currently \"\($0)\"")
    })

Good to go, right?

Not so fast my friend.

I had forgotten a very inconvenient truth:

UIKit, by and large, is not KVO-compliant.

😭

That means without KVO support, my idea doesn’t work. My testing confirmed it: my code never print()ed anything as I entered text in the field.

Thus, my fantasy of KVO eliminating much of the need for UIKit bindings was incredible, but short-lived.

Cancellation

The other problem I have with Combine is that it still isn’t terribly clear to me where/how Cancellable objects are supposed to be cleaned up. It seems that we’re supposed to keep a copy of these as instance variables. I don’t recall having read any official documentation about cleanup though.

(If you have, do let me know, please!)

In RxSwift, we had the awfully-named-but-ultimately-convenient DisposeBag. It’s trivial to create a CancelBag in Combine, but I’m still not 100% clear if that’s really the best approach.

Highs and Lows

All told, quite a lot was added to Combine in beta 2, and I am very excited to see what comes in future betas. Nonetheless, none of these new goodies have really swayed my opinions… yet.

In my next post, we’ll go ahead and cover how error handling works in RxSwift versus how it works in Combine, and the plusses and minuses of both approaches.