iOS
Rant: Xcode and the Protocol Paradox
Matt Neuburg
Written on February 23, 2021

This is a rant about an extremely useful Xcode feature that completely stops working just when you most need it to work. At the risk of giving the whole story away right at the start, I’ll just give the whole story away right at the start! The useful feature is Xcode’s ability to show you the chain of methods that call a given method, and the thing that makes it stop working is protocols.
Displaying the Call Chain
In the real-life world of practical programming, one of the most common questions you’re likely to have about code is this: you’re looking at a method, and you want to know who calls this method. This might be code that you yourself have written; it might be code that someone else wrote and that you’ve been called in to work on. Either way, knowing who calls, or might call, a given method is very frequently key to your understanding of how the program works.
Now, one way to find out the answer to that question is to put a breakpoint on the method and run the app. The app will pause at the breakpoint, and the call chain will be displayed in the Debug navigator. But that’s not as useful as you might suppose. It might not be easy or convenient to recreate the situation where this method is called in the first place. You might not know what that situation is; that might be the very problem you’re having. And even if you can recreate one such situation, that’s not the same as knowing every such situation. Also, the call chain displayed in the Debug navigator is not always as useful as one might hope (in asynchronous situations, for instance).
What you’d prefer, much of the time, is to ask the compiler about the call chain. That is a very reasonable approach. After all, one thinks, the compiler surely knows the answer to the question we’re asking; if it didn’t, it couldn’t compile the app! And sure enough, Xcode has some splendid built-in facilities specifically designed to help us in exactly this situation.
-
First, there’s the Related Items menu.
This is the menu that pops down from the funny four-squares icon at the top left of the editor, in the jump bar. This wonderful and probably under-utilized menu helps you find code that’s related in all sorts of ways to the current selection. If you’re like me and you prefer not to take your fingers off the keyboard more than you have to, you can pop this menu down with Control-1.
One of the menu items in this menu is Callers, and it displays (hierarchically off to the right) every class and method where the currently selected method is called.
-
Second, there’s the Find Call Hierarchy feature.
This extraordinarily cool Xcode feature is probably even more under-utilized! If you select a method and Control-click to get the contextual menu, you can choose Find > Find Call Hierarchy. You can also get the same effect directly in the Find navigator by playing with the search settings, plus this is also a normal menu item in the Find menu, where it even has a keyboard shortcut.
The effect of choosing Find Call Hierarchy is that you are shown, in the Find navigator, all the calling methods simultaneously, along with all of their calling methods, and so on, tracing back as far as possible for each, in a lovely hierarchical display, and you can then click on any of the listings to navigate to that method in the editor.
A Tiny Example
I’ll describe one of my own actual apps as a way of illustrating both of those features in use, by means of a very simple example — so simple, in fact, that the method in question is called by only one other method, and the call chain is only one level deep. In real life, the Xcode features we’re talking about will be exhibiting far more power. But even this trivial example will be sufficient to illustrate the Protocol Paradox when we come to it.
Okay, so in a game app, I have a Settings screen where the user determines various preferences. This screen is run by a view controller called SettingsController. One of the settings is a color. The user taps a color swatch, and another view controller, called ColorPickerController, is presented in front of the SettingsController. This is so the user can pick a different color (hence the name). In the ColorPickerController, the user can move sliders to change the current color and must then dismiss the modal presentation by tapping one of two buttons, either Done or Cancel.
When the user dismisses the modal presentation, at the latest, we obviously need to communicate the chosen color back to the SettingsController. How will we do that? Well, let’s pretend I’m a pretty unsophisticated programmer (not far from the truth). I know that the SettingsController is the presentingViewController
of the ColorPickerController. So I could simply have the ColorPickerController retrieve its own presentingViewController
, cast it down to SettingsController, and call a public SettingsController method, like this:
if let presenter = self.presentingViewController as? SettingsController {
presenter.colorPicker(self, didSetColorNamed: self.colorName, to: c)
}
In that code, c
is the color, or it will be nil
if the user tapped Cancel. The colorPicker(_:didSetColorNamed:to:)
method in the SettingsController interprets this color and is also responsible for calling dismiss
on the calling ColorPickerController; I won’t bother showing that code.
So now, let’s say I’m looking at SettingsController and the colorPicker(_:didSetColorNamed:to:)
method, and I’m wondering: How does this method ever get called? I can find out by looking at the Related Items menu:
Or I can find out by choosing Find Call Hierarchy:
So, as you can see, Xcode is pretty helpful here. Problem solved!
Enter the Protocol
Except for one thing. The way I’ve coded the communication between ColorPickerController and SettingsController, not to put too fine a point on it, sucks. Let’s face it, it’s no business of the ColorPickerController to know that its presenter is a SettingsController. Indeed, its presenter might not always be a SettingsController. And it’s certainly no business of the ColorPickerController to go peering into the internals of the SettingsController and knowing about all its methods.
The only thing ColorPickerController needs to know about whoever it is going to be talking to here is that that class implements this one method, colorPicker(_:didSetColorNamed:to:)
. That is the only information that needs to be public in order for this communication to work. One obvious way to express this exposure of a single method is to use the delegate-and-protocol pattern.
In this pattern, ColorPickerController itself declares a protocol — let’s call it ColorPickerControllerDelegate — that requires implementation of the colorPicker(_:didSetColorNamed:to:)
method. And ColorPickerController also has a (weak) instance property — let’s call it delegate
— whose type is an Optional ColorPickerControllerDelegate.
So now, when SettingsController presents ColorPickerController, SettingsController sets ColorPickerController’s delegate
property to itself — which it can do because it does indeed implement colorPicker(_:didSetColorNamed:to:)
and thus has been able to declare itself as an adopter of the ColorPickerControllerDelegate protocol. And on the other end of the communication, when ColorPickerController is ready to be dismissed, it talks to its own delegate, like this:
self.delegate?.colorPicker(self, didSetColorNamed: self.colorName, to: c)
Isn’t that nicer? Just as we intended, the fact that colorPicker(_:didSetColorNamed:to:)
is implemented by its delegate
is the only thing that ColorPickerController knows, and the only thing it needs to know, about its delegate
. Proper limitations are imposed on what each class knows about the other.
And Now, the Rant
There’s just one problem. The limitations on what ColorPickerController knows have also been imposed upon the compiler. The ColorPickerController delegate
property is typed as a ColorPickerControllerDelegate. So when ColorPickerController calls colorPicker(_:didSetColorNamed:to:)
on its delegate, it isn’t calling a SettingsController method at all; it’s calling a protocol method.
The result is a disaster for the problem we were originally trying to solve. When we return to the other end of the call chain and ask Xcode who calls SettingsController’s colorPicker(_:didSetColorNamed:to:)
method, Xcode’s answer is — nothing!
-
In the Related Items menu, the Callers menu item is disabled. This method apparently has no callers.
-
When you choose Find Call Hierarchy, the
colorPicker(_:didSetColorNamed:to:)
method is listed in the Find navigator, but that’s all. This method apparently has no callers.
But that is patently false. I just showed that there is a line where this method is called:
self.delegate?.colorPicker(self, didSetColorNamed: self.colorName, to: c)
The problem is that it is called on the protocol, not on SettingsController. So Xcode concludes that the SettingsController colorPicker(_:didSetColorNamed:to:)
method is never called.
The irony is that the compiler has additional information that it isn’t using here. To be sure, it doesn’t know that at runtime, when the ColorPickerController talks to its delegate
, that delegate
will in fact be the SettingsController. But it knows that it might be the SettingsController, because it has a complete list of all the adopters of the ColorPickerControllerDelegate protocol — and that list includes the SettingsController. (In fact, in this particular instance, it includes only the SettingsController.) So you’d think that Xcode could at least provide a list of places where this method might be called, instead of providing no information at all.
Moreover, consider what happens when we go the other way. In ColorPickerController, Command-click on the call to colorPicker(_:didSetColorNamed:to:)
to see where it is declared:
Xcode displays two possibilities. One is that we are calling the colorPicker(_:didSetColorNamed:to:)
declared in the ColorPickerControllerDelegate protocol. But the other is that we might be calling the colorPicker(_:didSetColorNamed:to:)
declared in SettingsController.
So it turns out that Xcode does know, from the point of view of ColorPickerController, that a call to colorPicker(_:didSetColorNamed:to:)
might arrive at SettingsController. Well then, from the point of view of SettingsController, why doesn’t Xcode know, or why can’t it at least guess or suggest as a possibility, that the colorPicker(_:didSetColorNamed:to:)
method is possibly called by ColorPickerController?
The Real World
The reason all of this is a problem is that in the real world the use of protocols as a way to list the public channels of communication between objects is extremely pervasive.
For example, if you want to test an object, you might choose, for one reason or another, to mock that object. In that case, you might refer to that object in your code by way of a protocol that is adopted both by the real object type and by the mock object type. That way, the mock object can be substituted at testing time for the real object (“dependency injection”) and the rest of the code goes on working, effectively agnostic about the real class of the object it’s talking to.
Or, you might be implementing a sort of Model–View–Presenter architecture. Such an architecture comes in many flavors, but in general the idea would be that the decision-making logic is moved out of the view controller into a helper class, which we can call the Presenter. When the user does something, the view controller hears about it and turns to the Presenter to tell it what happened, letting the Presenter worry about what the response should be. When the displayed data needs to change, the Presenter turns to the view controller and tells it what to display, letting the view controller worry about the actual subviews that will do the displaying. We thus end up with a repertoire of intents — things that the view controller might say to its Presenter, and things that the Presenter might say to its view controller. Rather than exposing the two classes directly to one another, it is common good practice to codify that repertoire as a pair of protocols — not just because it regulates privacy appropriately, but also because, once again, it’s good for testing.
If you’ve written any real-world apps, you know all of that. Those are patterns you are accustomed to; they are “best practice.” But those best practices, as I have just shown, effectively break your ability to understand and debug your own code with Xcode, because Xcode no longer knows who is calling the methods declared in the protocol.
So on the one hand, you want to use protocols. On the other hand, they break Xcode’s ability to help you reason about your code, so you don’t want to use them. Protocols: can’t live with them, can’t live without them. That’s the Protocol Paradox.