Smart Proxy Delegation
When calling optional delegates, the regular pattern is to check using respondsToSelector:, then actually call the method. This is straightforward and easy to understand:
1 2 3 4
Now, this used to be three lines and now it’s four lines, because
delegate is usually weak, and once you enable the relatively new Clang warning,
-Warc-repeated-use-of-weak, you will get a warning when accessing self.delegate more than once. All in all, that’s a lot of boilerplate for a simple selector call.
In the past, I’ve used an approach similar to what Apple does with parsing the delegate when it’s being set and caching the
respondsToSelector: calls. But that’s even more boilerplate, cumbersome to update once you change a delegate, and really not worth it unless you call your delegates 100x per second.
What we really want is something like this:
We can use NSProxy to simply forward the method if it’s implemented. This used to be expensive, but with Leopard Apple came Fast Message Forwarding. This “can be an order of magnitude faster than regular forwarding” and doesn’t require building an NSInvocation-object.
Our custom NSProxy is really quite simple and just a few lines of code. The important part is here:
1 2 3 4 5 6 7 8 9 10 11 12 13
One sad detail is that if the method is not implemented in the delegate, message forwarding will use the slow path with
forwardingTargetForSelector: interprets both ‘nil’ and ‘self’ as ‘continue with message forwarding.’
Implementing The Delegate Proxy
We want to make sure that implementing this in our classes is simple. Here’s the pattern I use in PSPDFKit:
1 2 3 4 5 6 7 8 9
You’ll notice a few things. First, we lie about the actual type of
delegateProxy. It’s a class of type
PSTDelegateProxy, but we declare it as the delegate type so that we don’t have to cast it every time we use it, and so that we also get compiler-checked warnings when there are typos in the selector. Notice that the proxy needs to be strongly retained.
Second, we’re using a macro to simplify things. This macro expands into following:
1 2 3 4 5 6
We keep the weak reference of the delegate directly in the PSPDFDelegateProxy; no need to save it twice. This macro only works if you name your delegate
delegate, but that should be the common case, and you could expand the macro to cover different cases. We do keep a strong reference of our proxy-object around, but this won’t hurt. Other solutions work with weak-saving NSProxy, but that’s not needed and also buggy on iOS 5.
Now we’ve already covered most of the cases. But there’s one pattern that we still need to take care of, which is optional methods that change a default return value if implemented:
1 2 3 4 5
If the proxy can’t find a delegate, it will return
nil (id), NO (BOOL), 0 (int), or 0.f (float). Sometimes we want a different default. But again, we can perfectly solve this with some more NSProxy trickery:
And here’s the relevant code for the subclass that is being returned after calling
1 2 3 4 5 6 7
We have to to go down to NSInvocation, which is a bit slower, but again, you won’t notice, except your delegate is called many times per second, which is quite unusual.