bits about life, coding and stuff
If you call an optional delegate protocol, and it grows larger and larger, you find yourself write variations of this ugly block all over your code:
I consider this code smell, and it doesn’t get easier once you decide to change the delegate. While developing PSPDFKit (a commercial, extremely fast iOS pdf framework), i found myself in the situation of polluting my code with those delegate calls all over the place. There has to be a better way.
During my Twitter research, I fond some experiments like using an NSProxy or using _cmd as a shortcut. Using _cmd would imply that you name your delegate caller the same as your delegate method. THATS A VERY, VERY BAD IDEA. It’s often practical to extend a class and set the delegate to itself – in fact that’s the suggested way to customize PSPDFViewController. Using the _cmd trick would imply that the user calls [super ---] on every delegate – forgetting that would work most times, except when he tries to nil out the delegate on viewWillDisappear, and still receive messages (because there is no caller any more) – leading to very confusing errors.
A pattern that’s common in Apple classes is to pre-analyze the delegate, and remember which methods are implemented and which aren’t. I did some profiling and discovered that this is about twice as fast. In reality, you may not care, delegates usually are not called in tight loops, where you could actually gain some feelable performance with this task.
But we gain something else: clarity. Here’s my proposal:
So, this is much more code, what’s better?
Bonus feature: Because there’s now a single entry point, it’s easy to add a one-time deprecation warning (like an NSLog) to the delegate caller. That would be a PITA if you have multiple entry points.
I also experimented with further improvements, and you can squeeze out even more performance by using c-functions, and again more if you inline them. But it’s usually not worth it and the calling syntax doesn’t look nice.
Btw, I used mach_absolut_time to measure execution time. Don’t use NSDate for it, it’s too unprecise. (Some technical stuff: I used a tight 1-million for-loop to get some milliseconds to measure, and disabled optimization so that the if couldn’t be pre-optimized)
I’m open to comments. Is there a better way? Waste of time? Hit me up on Twitter! @steipete
Related posts:
8 Responses to Fast and elegant delegation in Objective-C
Rafael
September 7th, 2011 at 6:19 pm
I understand the problem, but I don’t like the solution. It makes a tedious task tedious. One optimization would be to store references to the delegate itself or nil, instead of flags, but this would make the calls look horrible: [delegateFlags_.delegateDidShowPage pdfViewController:self didShowPage:page];
There has to be a more elegant solution for this problem, but it’s already late afternoon. Why not just use a macro?
Sneakyness
September 8th, 2011 at 7:12 pm
So did you measure the time in vodka? “mach_absolut_time” ;P
Dave Murdock
September 8th, 2011 at 8:24 pm
That’s awesome. Definitely going to start using this, a smart optimization.
Cédric Luthi
September 8th, 2011 at 10:33 pm
The most elegant solution to this problem is in my opinion the ifResponds higher order message:
[[self.delegate ifResponds] pdfViewController:self didDisplayDocument:self.document];
studpete
September 11th, 2011 at 12:42 pm
That’s indeed a very great solution, if performance is not a problem (which usually isn’t)
Here’s a link that explains this pattern: http://jpobjc.wordpress.com/2010/08/15/my-higher-order-message-implementation/
Still, I like my method for having a single entry point, which lets you calculate stuff for the delegate call.
Rafael
September 11th, 2011 at 1:09 pm
@Sneakyness: I prefer Scotch over Vodka
Cédric’s solution is indeed the most elegant one.
Vadym Pilkevych
October 6th, 2011 at 8:12 pm
This is not about the performance, isn’t it? So the only aim I’ve seen here is to decrease the number of lines of code. Looks good for the framework, but most of the time you’re not developing frameworks I suppose. Usually I have to deal with a delegate which has got a few methods to call. Most likely these methods are being called each from one separate place rather then from multiple places. So, it’s not that hard to stay with an old pattern. I think your approach is a bit cumbersome.
Tono
October 7th, 2011 at 12:21 pm
Instead using the ifResponds HOM, you could use a delegate Proxy. Such a proxy would be initialized with the real delegate in setDelegate: and this proxy is then kept in the iVar and sent messages. It could be the proxies responsibility the check weather the real delegate responds to a selector and invoke it if appropriate. You could then call optional protocol messages without even thinking about.