views:

131

answers:

3
+1  Q: 

Call back style

I am writing an iPhone application which in numerous places needs to perform non HTTP or FTP networking of a very simple request response type.

I've wrapped all this up into a SimpleQuery class that integrates with the run loop.

SimpleQuery *lookup = [[SimpleQuery alloc] init];
[lookup setDelegate:self];
[lookup doQueryToHost:queryServer port:queryPort query:queryString ];

As you can see the calling object sets itself as a delegate. When the results are complete it then calls a method on the delegate with the results.

[delegate simpleQueryResult:resultString simpleQuery:self];

I am now in a position where I have a user of SimpleQuery that has two types of query so I need to extend SimpleQuery to support this.

I can think of two sensible ways of doing this.

Firstly passing a selector into doQueryString, or a seperate doQueryStringWithSelector.

[lookup doQueryToHost:queryServer port:queryPort query:queryString selector:@SEL ];

Secondly passing a tag into doQueryString so that when the delegate is called it can query the tag, as the simpleQuery is passed, to find out what the results are for.

[lookup doQueryToHost:queryServer port:queryPort query:queryString withTag:tag ];

I'm just wondering which is best from a coding style perspective, the first seems simpler but tagging seems more in keeping with the iPhone SDK and Interface Builder

+1  A: 

There's a third option that's a common pattern in the kits, which is to use @protocols.

For example:

@protocol QueryCompleteHandlerProtocol
- (void)queryType1Complete:(int)intStuff;
- (void)queryType2Complete:(float)floatStuff;
@end

What this does is declare a set of method calls that an object adopting the protocol has to conform to (the compiler will actually enforce this).

So your SimpleQuery object will hold on to something like the delegate pointer, which you might declare like this among the ivars:

NSObject<QueryCompleteHandlerProtocol> *callback;

What this tells the compiler is that callback is an object that descends from NSObject and adopts the QueryCompleteHandlerProtocol protocol. Sometimes you see this written as:

id<QueryCompleteHandlerProtocol> callback;

When you want to call the callback there's nothing special about them, SimpleQuery's methods will just call:

[callback queryType1Complete:1];
[callback queryType2Complete:2.0];

Finally you client for the procotol class will declare itself as adopting the protocol:

@interface MyClass : NSObject<QueryCompleteHandlerProtocol>
...
@end

And will set itself as the callback with some code like:

[lookup setCallback:self];

This is where the compiler checks that MyClass conforms to QueryCompleteHandlerProtocol, meaning it has implemented queryType1Complete: and queryType2Complete:.

duncanwilcox
-(void)simpleQuery:(SimpleQuery *)sq completeWithValue:(NSValue *)value; would be more like Apple's convention for designing delegate protocols. One could also add -tag/-setTag: methods to SimpleQuery to tell them apart
rpetrich
Protocols unfortunately won't work as the SimplyleQuery object doesn't know what the query is for. So it couldn't decide which of the protocols to use. Should have made that clearer I am already using a protocol for the single callback definition at the moment.
Dean Smith
It sounds like you're putting some code in the SimpleQuery class just for the purpose of having it all in one place. If you also allow the class to abstract functionality a bit and let it know about semantics of what it's doing (e.g. know what the query is for) you might end up with less generality in the calling code which in turn would make the SimpleQuery API simpler.
duncanwilcox
+2  A: 

An option which is used commonly in Apple's code (for example, in UIControl) is to provide both a target object and a selector. This works only if there is a single callback, and is more appropriate than a delegate in that case. (If there are multiple callbacks, then you'll probably have to go with a delegate and the tag approach.)

If you go this route, then you do away with the delegate altogether and instead have a method with a signature like this:

doQueryToHost:(id)queryServer port:(int)queryPort query:(NSString*)queryString target:(id)target action:(SEL)action

Note that "action" is typically preferred over "selector" in methods arguments in this case. The query would simply call the selector on the target when done. This would allow your clients to have multiple selectors, and also multiple target objects; this can help clean up code because you don't need to shove everything into a single delegate object.

If you want to go with your tag route, you should call it "context", which is what Apple uses (for example, in addObserver:forKeyPath:options:context).

Jesse Rusak
+1  A: 

I'm not sure I understand the problem here. Can't SimpleQuery's user just set another delegate object for the second query, or branch on the simpleQuery: parameter? That's a basic part of the delegate pattern, just like having two UIActionSheets for one view controller.

Brent Royal-Gordon
I should have made clear that an instance SimpleQuery only ever has one delegate. This issue is that one kind of delegate uses SimpleQuery for two different types of query so when the results are returned via the simpleQueryResult function the delegate needs to know what the results are for.
Dean Smith
I think Brent meant that you could just make a second SimpleQuery object for the second query, then have another delegate for it, or branch on the simpleQuery: parameter.
Jesse Rusak