Objective-C Delegate Optimization, Part 2
Since my previous entry on optimizing Objective-C delegates, I ran across an article on borkware.com called Elegant Delegation. The article goes over a technique that uses a proxy to forward messages to the delegate, called MDelegateManager
. The proxy just eats the message if the delegate does not respond to it. This is a very clever and elegant solution, especially if the delegate does not return any value.
I actually tried to implement something similar, but could not figure out a way to stop the "selector not recognized" exception. The magic is the "undocumented" signatureWithObjCTypes:
method of NSMethodSignature
. I put that in quotes because this method has been known for some time now. Apparently, anyone doing anything with proxies is already aware of it. Though, now that I understand why it's used, we can actually use public methods for this case (I'll cover that later).
In this second part, I'll go over how MDelegateManager
stacks up against my other techniques in readability and performance. I'll also see if using a C++ map, instead of an NSMutableDictionary, can help the miserable performance of my respondsToSelector:
cache, by avoiding boxing and unboxing of selectors and booleans into objects.
So, let's cover readability of MDelegateManager
first. Like I mentioned above, if the delegate does not return anything, this really shines. Your just call your delegate on the proxy, and you don't have to worry if the delegate doesn't implement it (just set it, and forget it!). But if your delegate returns a value, you first have to check if the delegate was actually called, before using the return value. Continuing with my examples from last time:
- (void) someMethod {
BOOL shouldProceed = [delegate operationShouldProceed];
if (([delegate justRespondeded]) && (shouldProceed) {
// do something appropriate
}
}
This isn't so bad for a BOOL
, but if you're returning another object or an NSRect
, you may have to resort to more compilcated if statements. I personally prefer my technique of refactoring respondsToSelector:
into it's own method, since that gives you the opportunity of providing a default return value, if the delegate isn't implemented. This eliminates the special case from someMethod
in sort of a null object design pattern of sorts. So, in my opinion, the proxy technique is not any more readable than my refactoring technique.
How does it stack up on performance? Unfortunately, it's the slowest of the lot, coming in at over 100 times slower than the ivar cache technique. This pretty much rules out this technique for me. However, as I mentioned, the performance probably isn't noticeable in real world scenarios, and if you think this is more readable than refactoring
So, finally, for grins, I decided to see if I could optimize my generic ivar cache class. My first implementation used an respondsToSelector:
into it's own method, then MDelegateManager
may be for you. Oh, and if you want to use all public methods, just replace the NSMutableDictionary
of booleans, indexed by selector. Unfortunately, both the selector and the boolean had to be wrapped in NSValues
to be used, since they aren't Objective-C objects. Creating these temporary NSValue
objects really slowed things down, though. The standard C++ map does not have this limitation, and you can directly use them using std::map<SEL, BOOL>
. This does speed things up significantly, but it's still 1.58 times slower than the ivar cache technique. It's also not all that readable, so I probably won't use this technique, either. Thus, I think I'll stand by my original conclusion from part one.