Here’s the problem: the binding retains the toObject object. Which means that the button effectively retains the controller. If the controller retains its button (it should), then there’s a retain cycle. Neither will become zombies, but they could become abandoned.

One way to crash — and this is a true story — is if the abandoned controller listens for a notification (call it BSNotification), and it Does Something when receiving a BSNotification, and when it Does Something it crashes, because it’s no longer conceptually valid and it doesn’t know how to deal with the current state of things.

KVO means having to do everything perfectly every time

Let’s add a third object, a model object. What we really want is this flow:

Okay — now we have the entire flow. When modelObject.title changes, that affects controller.title, and button.title updates with the correct value.

Very convenient, and it works wonderfully.

It will crash, of course, when modelObject is deallocated (because an instance of modelObject was deallocated while it still has an observer).

If, instead, controller is retaining modelObject (as it probably should be), then you have a third object that will be abandoned and never deallocated, and it will sit around stewing and growing eviler by the minute.

One way to solve the problem that isn’t that great

The controller could have a method with a name like invalidate that breaks the retain cycles. Once broken, then dealloc will eventually be called for the controller, its button, and its model object.

You might write code like this, which you call when you know for a fact you’re finished with the controller:

Reference counting is a nice solution — it guarantees that when dealloc is called, you know that no object has a strong reference to the object being deallocated. This makes dealloc a great place to remove observations and similar that need removing.

But if you use something like an invalidate method, you’re trying to do the work of reference counting yourself. You have to call invalidate, and you have to call it at the right time. Can you make that guarantee forever, for every object that has an invalidate method? What if something changes so that more than one object retains the controller? Who calls invalidate, and when?

That’s a lot of extra work and thinking, and part of the goal of programming is to make errors less likely. Relying on invalidate makes errors more likely.

A better way to solve the problem

Let’s also be clear: controller knows about modelObject and button, but neither of those two know about each other, and neither of those two know about the controller. Here’s how we might handle it without the need for an invalidate method.

In the controller, nuke the custom getter. Nuke keyPaths​ForValues​AffectingTitle. Nuke the use of bind:​toObject:​withKeyPath:​options:.

Instead, create a custom setter — because, after all, setters are called when something changes, and the entire problem to solve is propagating changes to a title property.

Review and recommendations

The solution we came up with fixes the retain cycle without your having to remember to call an invalidate method and call it at the exact right time. It’s safer code.

There’s a good chance it’s less code, too, and it’s more explicit code.

Some recommendations:

Don’t use bind:​toObject:​withKeyPath:​options: ever, in any circumstance. (iOS people: consider yourselves lucky that it’s not an option. Also consider that there’s probably a reason it never made it to iOS.)

Use a custom setter rather than a custom getter when you’re propagating changes. (It’s in the setter where a thing changes, after all.)

Avoid invalidate methods in favor of letting reference counting do its thing — because if you are the one trying to track references, you’re going to make mistakes. (I realize avoiding invalidate methods isn’t always possible, but it’s probably more possible than you think it is.)

Interlocking observations of any kind make it difficult to think about what happens in your app: it’s better to be explicit whenever it’s reasonable. Once you get enough of these tendrils of observations you’ve built an impenetrable jungle, and making changes becomes scary.

In theory, bindings and KVO are there to promote loose coupling, but in practice the coupling is often just as tight — if not tighter, in a sense — and harder to debug and get right. It’s generally best to do explicit observations (as opposed to keyPaths​ForValues​AffectingXyz) and keep your keyPaths free of . characters.