Objective-C: Blocks vs. Selectors vs. Protocols
I frequently find myself writing "utility" classes that can be re-used throughout my projects.
For example, suppose I have an "Address Book" view. I might want to use my address book to select who gets sent an email, or maybe who gets added to a m开发者_StackOverflow中文版eeting request.
I'd develop this view controller so it can be used by both the email controller, and the meetings controller, with some sort of callback mechanism to let the caller know the user either finished selecting someone from the address book, or they canceled.
It seems there are basically four (reasonable) approaches one might take in this scenario;
Create an "AddressBookDelegate" protocol and a corresponding delegate property on the AddressBookController. Then use the messages defined in the protocol to communicate the result (similar to UIActionSheetDelegate).
Create an "informal" "AddressBookDelegate" protocol and a corresponding delegate property on the AddressBookController, but the type of the delegate property will be "id", and will check at runtime with "respondsToSelector:" to see if the delegate implements the methods we require (seems like most of the framework stuff has started going this way).
Pass the AddressBookController an id that represents a delegate, as well as two SELs which specify the methods to call when the user selects a user or cancels the request. The benefit I see with this is; suppose one controller supports BOTH sending emails AND setting up meetings (I know in this example that seems like bad design... but one can imagine a more generic situation where this would seem perfectly reasonable for a utility class) - In this case you could pass the AddressBookController different SELs depending on whether you're adding users to an email, or adding users to a meeting... a huge improvement over an iVar to indicate the controller's "state".
Pass the AddressBookController two blocks; one to run when the user selects someone from the address book, and one to run if the user cancels the request.
The blocks have been so tremendously useful to me, and SO much more elegant, I'm finding myself almost confused over when to NOT use them.
I'm hoping more experienced members of the StackOverflow community than I can help out with their thoughts on this topic.
The 'traditional' way to do this is with a protocol. Informal ones were used before @protocol was added to the language, but that was before my time and for at least the last few years informal protocols have been discouraged, especially given the @optional specifier. As for a 'delegate' which passes two SELs, this just seems more ugly than declaring a formal protocol, and generally doesn't seem right to me. Blocks are very new (esp. on iOS), as these things go, and while we have yet to see the tremendous volume of documentation/blogs on the best tried and true style, I like the idea, and this seems to be one of the things blocks are best for: neat new control flow structures.
Basically what I'm trying to say is that each of these methods vary in age, with none being better than the last except for style, which obviously counts for an awful lot, and is ultimately why each of these things was created. Basically, go with the newest thing you feel comfortable with, which should be either blocks or a formal protocol, and that your confusion is most likely coming from reading conflicting sources because they were written at different times, but with time in perspective, it is clear to see which supersedes the others.
[Controller askForSelection:^(id selection){
//blah blah blah
} canceled:^{
//blah blah blah
}];
is probably a hell of a lot more concise than defining two extra methods, and a protocol for them (formally or otherwise) or passing the SELs and storing them in ivars, etc.
I would just go with your first approach. It's a tried and true pattern in Cocoa, and seems to fit very well into what you're doing.
A few comments on the other approaches:
- Informal protocol - I don't really see any advantage of doing this over a formal protocol. Every since formal protocols gained
@optional
methods, the utility of informal protocols is much less. - Passing SELs - I don't think this is an established pattern in Cocoa. I personally wouldn't consider it as better than the delegate approach, but if it fits your thinking better, then go for it. You're not really getting rid of state; you're just transforming into something else. Personally, I'd prefer to have an ivar that I can set and check without having to use selector types.
- Passing blocks - This is sort of a new-age approach, and it has some merit. I think you need to be careful though because, in my opinion, it doesn't scale really well. For example, if NSTableView's delegate and data source methods were all blocks, I would personally find that somewhat annoying. Imagine if you wanted to set 10 different blocks, your
-awakeFromNib
(or whatever) method would be pretty big. Individual methods seem more appropriate in this case. However, if you're sure that you're never going to go beyond, say, two methods, then the block approach seems more reasonable.
精彩评论