Objective-c asynchronous communication: target/action or delegation pattern?
Asked Answered
P

3

14

I'm dealing with some asynchronous communication situations (Event-driven XML parsing, NSURLConnection response processing, etc.). I'll try to briefly explain my problem:

In my current scenario, there is a service provider (that can talk to a xml parser or do some network communication) and a client that can ask the service provider to perform some of its tasks asynchronously. In this scenario, when the service provider finishes its processing, it must communicate back the results to the client.

I'm trying to find a kind of pattern or rule of thumb to implement this kind of things and I see 3 possible solutions:

1. Use the delegation pattern: the client is the service provider's delegate and it will receive the results upon task completion.

2. Use a target/action approach: The client asks the service provider to perform a task and pass a selector that will have to be invoked by the service provider once it has finished the task.

3. Use notifications.

(Update) After a while of trying solution #2 (target and actions), I came to the conclusion that, in my case, it is better to use the delegation approach (#1). Here are the pros and cons of each option, as I see them:

Delegation approach:

  • 1 (+) The upside of option 1 is that we can check for compile-time errors because the client must implement the service provider's delegate protocol.

  • 1 (-) This is also a downside because it causes the client to be tight-coupled with the service provider as it has to implement its delegate protocol.

  • 1 (+) It allows the programmer to easily browse the code and find what method of the client, the service provider is invoking to pass its results.

  • 1 (-) From the client point of view, it is not that easy to find what method will be invoked by the service provider once it has the results. It's still easy, just go to the delegate protocol methods and that's it, but the #2 approach is more direct.

  • 1 (-) We have to write more code: Define the delegate protocol and implement it.

  • 1 (-) Also, the delegation pattern should be used, indeed, to delegate behavior. This scenario wouldn't be an exact case of delegation, semantically speaking.

Action/Target Approach

  • 2 (+) The upside of option 2 is that when the service provider method is being called, the @selector specifying the callback action must also be specified, so the programmer knows right there which method will be invoked back to process the results.

  • 2 (-) In opposition to this, it's hard to find which method will be called back in the client while browsing the service provider code. The programmer must go to the service invocation and see which @selector is being passed along.

  • 2 (+) It's a more dynamic solution, and causes less coupling between parts.

  • 2 (-) Perhaps one of the most important things: It can cause run-time errors and side effects, as the client can pass a selector that does not exist to the service provider.

  • 2 (-) Using the simple and standard approach (#performSelector:withArgument:withArgument:) the service provider can only pass up to 2 arguments.

Notifications:

  • I wouldn't choose notifications because I think they are supposed to be used when more than one object need to be updated. Also, in this situation, I'd like to tell directly the delegate/target object what to do after the results are built.

Conclusion: At this point, I would choose the delegation mechanism. This approach provides more safety and allows easily browsing the code to follow the consequences of sending the delegate the results of the service provider actions. The negative aspects about this solution are that: it is a more static solution, we need to write more code (Protocol related stuff) and, semantically speaking, we're not talking really about delegation because the service provider wouldn't be delegating anything.

Am I missing something? what do you recommend and why?

Thanks!

Paulapauldron answered 22/12, 2009 at 17:15 Comment(0)
L
3

You did miss a third option – notifications.

You could have the client observe for a notification from the service provider indicating that it has new data available. When the client receives this notification it can consume the data from the service provider.

This allows for nice loose coupling; some of the decision is just down to whether you want a push/pull system though.

Looney answered 22/12, 2009 at 19:54 Comment(1)
Thanks Ciarán, I did not weigh Notifications just because I think they should be used when more than one object need to be notified. Furthermore I think that in this kind of situations, it is better to be able to talk directly with the client (from the service provider point of view) to inform that the operation is finished. But sure, Notifications is another way of handling this. Perhaps I'll update the question later including notifications, but for now I want to see what happens.. :D Thanks again!Paulapauldron
O
0

Very good question.

I dont think I am qualified, just yet (as I am a newbie), to comment on which design pattern is better than the other. But just wanted to mention that the downside you mentioned in point 2 (runtime exception) can be avoided by

if([delegate respondsToSelector:callback]){
    //call to callback here
}

Hope that helps to weigh the options

Ogdan answered 22/12, 2009 at 19:0 Comment(3)
Thanks Mihirsm, You're right, that will prevent a run-time error. But still, the fact that the programmer used a wrong selector will not be visible until run-time. That could create side effects :S. Again, a plus for the, more static but secure, delegation mechanism. Cheers!Paulapauldron
You can make this a bit safer by implementing a @protocol, and having the delegate conform to this protocol. This will ensure that the delegate class implements the required methods. You could also add an assertion into your setDelegate: method to test that the new object conforms to the protocol.Bookman
The default way for methods declared under protocols is for them to be @optional, so [delegate respondsToSelector:callback] is still necessary. You can use @required in your protocol, but I've found most people don't do that. My solution was to create an NSProxy trampoline returned by an NSObject extension, -[NSObject ifResponds]. Just syntactic sugar that lets you not have all of that if nonsense.Reichsmark
A
0

Another downside for the Delegation approach: A service provider can only have one delegate. If your service provider is a singleton, and you have multiple clients, this pattern does not work.

This caused me to go for the Action/Target approach. My service provider holds state and is shared among multiple clients.

Asoka answered 6/7, 2010 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.