The Situation
My custom controller class has the following method:
- (void)requestViewControllerWithIdentifier:(NSString *)identifier fromObject:(id)object;
This causes object to receive this message:
- (UIViewController *)viewControllerWithIdentifier:(NSString *)identifier;
The Problem
Right now, object is just an id so there's no guarantee that it actually implements that method, and I get a compiler warning when I send the message.
The Solution
I came up with two solutions myself:
Use a protocol.
Make id an NSObject and create a category for NSObject.
They are both fine solutions probably and I don't mind choosing one of them, but...
The Question
...I noticed Apple is doing something odd in their GameKit API. GKSession has the following method:
- (void)setDataReceiveHandler:(id)handler withContext:(void *)context
handler is just an id, but Apple actually requires it to implement this method:
- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context;
Without making use of any protocol or category! I'm wondering how and why would they do this? Why don't use a protocol? Do they enforce the method in some other way? If I were to do this, how can I suppress the compiler warning?
You can assign a variable of type id to any object type. If you know that it must implement a given protocol you can assign it to a variable of that type within the method and invoke the method on that variable.
As a design point, I would say that it is better to make the protocol explicit and externalise it to the caller so that the compiler can do type checking properly. Some parts of Apple's code are better than others at this: from what you say GameKit is very much at the unhelpful end of things.
Defining a category is not what you want to do, because that tells the compiler that you will add the method to every NSObject.
If you have this protocol:
#protocol YourProtocol <NSObject>
- (UIViewController *)viewControllerWithIdentifier:(NSString *)identifier;
#end
And define your method as:
- (void)requestViewControllerWithIdentifier:(NSString *)identifier fromObject:(id <YourProtocol>)object;
It will probably do what you want. It may not be strictly necessary in this case but it's usually a good idea to have your protocol extend the NSObject protocol (as above) so that you can call useful stuff like -retain, -release, -autorelease, and -respondsToSelector. The alternative of declaring the method as follows prevents the user from using an NSProxy-rooted object as the object parameter.
- (void)requestViewControllerWithIdentifier:(NSString *)identifier fromObject:(NSObject <YourProtocol> *)object;
If it's just for your own use and you aren't using proxy objects this can be quite convenient, but you should avoid it in public APIs.
I think the answer you seek is the difference between a formal and an informal protocol. This answer has a good explanation.
Related
Often, the autocomplete in Xcode will autocomplete the method names for that class, and method names if the class in a delegate of some other object like this:
#interface ViewController : UIViewController <UIAlertViewDelegate,
GKPeerPickerControllerDelegate>
But for the method of Game Kit, to be inside of ViewController.m:
- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer
inSession: (GKSession *)session context:(void *)context {
// ...
}
It doesn't seem to be part of any delegate methods, either by Xcode's autocomplete or in any documentation? If it is not part of a delegate, why is it different from the way all other delegate method works?
Update: If I do a search for all the header files:
grep -r receiveData /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk
The only file that contains that line is:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk/System/Library/Frameworks/GameKit.framework/Headers/GKSession.h:- (void)setDataReceiveHandler:(id)handler withContext:(void *)context; // SEL = -receiveData:fromPeer:inSession:context:
so it only shows up in a comment...
The documentation for the setDataReceiveHandler:withContext: method in the GKSession class says:
The handler must implement a method with the following signature:
- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context;
This doesn't answer your question of why this method isn't implemented like a normal delegate method. I suppose this implementation provides more flexibility because you can have one object respond to GKSession delegate methods that manage the connection, while designating a different object to handle incoming data once the connection is made.
In any case, hopefully this answers the practical question of where this method is defined, which had me scratching my head for a while.
I suspect it's different because the API dates from an early version of Objective C (originally, protocols were less strongly-typed, and most callback methods were defined in this ad-hoc, non-compiler-checked fashion).
Most (almost all) of the core API's have been updated so that all protocols are explicit, all callbacks are typed - I guess they missed this one.
I have an NSOperation that wraps some web service functionality. The NSOperation has a delegate that is to be messaged when the operation is over.
As the NSOperation lives on a different thread I have to make the call like this:
[delegate performSelectorOnMainThread:#selector(getDealersIDSuccess:) withObject:result waitUntilDone:YES];
It works just fine, but it gives me a warning:
warning:
'-performSelectorOnMainThread:withObject:waitUntilDone:'
not found in protocol(s)
I completely agree with the compiler on this one, it sees a delegate, it checks the protocol, it finds no declaration of a performSelector method.
My question is: can I remove the warning by making this call in a different manner?
My two guesses are that I could (1) write a method called
- (void) callDelegateMethodOnMainThred {
[delegate getDealersIDSuccess:result]
}
and call that through performSelectorOnMainThread, but I find that solution to be cumbersome and an extra, hard to read, step on top of the delegation.
The second solution could be to cast the delegate to the type of my parent object inside the selector, but that is just plain crazy and goes against the delegate encapsulation pattern.
I would really appreciate a third solution from someone with a better understanding of the language:)
Thank you in advance.
EDIT: Added delegate declaration:
id <ISDealersIDDelegate> delegate;
I declare my delegate as id. The delegate it self extends UIViewController.
I could see that declaring it NSObject would work.
performSelectorOnMainThread:withObject:waitUntilDone: method is declared in NSObject class. If your delegate object inherits from NSObject you can declare it as
NSObject<MyDelegateProtocol> *delegate;
So compiler will know that delegate responds to NSObject's methods and won't issue a warning.
It might be even a better solution not call performSelectorOnMainThread: on a delegate or other protocol implementation.
Make it the responsibility of the delegate/receiver to determine if it needs to do things on the main thread.
[delegate performSelector:#selector(delegateAction:)
withObject:actionData];
Delegate implementation
- (void)delegateAction:(NSData*)actionData
{
[self performSelectorOnMainThread:#selector(updateUIForAction:)
withObject:actionData
waitUntilDone:NO];
}
- (void)updateUIForAction:(NSData*)actionData
{
// Update your UI objects here
}
It might look like more code, but the responsibility is in the right place now
Actually on iOS 4 I prefer using NSNotifications and Observers (with Blocks) to perform updates on the mainthread.
- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *))block
I wrote down my design here.
1) Declare your delegate's protocol to extend the NSObject protocol in the .h file
#protocol YourDelegateProtocol <NSObject>
2) Cast to NSObject in your call
[(NSObject*)delegate performSelectorOnMainThread:#selector(getDealersIDSuccess:) withObject:result waitUntilDone:YES];
I definitely recommend number (1).
I have this inside a class
[delegate performSelector:#selector(doStuff:) withObject:myObject afterDelay:2.0];
and I am having this error
warning:
'-performSelector:withObject:afterDelay:'
not found in protocol(s)
I cannot figure out what may be wrong.
any clues?
thanks.
Your problem is that you've declared your delegate instance variable as:
id<SomeProtocol> delegate;
Right? Well, that means that the compile-time checker is only going to look for methods in the <SomeProtocol> protocol. However, performSelector:withObject:afterDelay: is declared on NSObject. This means that you should declare the ivar as:
NSObject<SomeProtocol> * delegate;
This is saying that it must be an NSObject that conforms to <SomeProtocol>, as opposed to any object that conforms to <SomeProtocol>. This should get rid of your warning, and you don't have to do any casting.
Try casting the delegate to its class' type first, then invoke performSelector:withObject:afterDelay:
[(SomeClass *) delegate performSelector:#selector(doStuff:) withObject:myObject afterDelay:2.0];
Assuming that your delegate is of type id, you need to tell the runtime that the object in fact does inherit from NSObject (where the performSelector:withObject:afterDelay: method is defined) by casting it to it's class' type.
Although the NSObject * cast Dave brought up will work, there's a cleaner alternate way that lets people use protocols as they would expect - you can have the protocol itself declare it supports the NSObject protocol.
NSObject is not just an implementation but also a protocol that includes all of the performSelector: method variants. So you can simply declare in your protocol that you support NSObject like you would any other class supporting a protocol:
#protocol MyProtocol <NSObject>
And the compiler warnings will vanish (assuming you have imported the protocol definition).
Note that this does mean going forward anyone that declares support of your protocol would pretty much have to be an NSObject or inherit a lot of methods. But all classes used in iPhone programming are derived from NSObject due to practical considerations, so that's generally not an issue.
EDIT:
It turns out the -performSelector:withObject:afterDelay: is not in the NSObject protocol (the ones without the delay are). Because it's still nicer to everyone using your protocol if they can just use id to reference a protocol type, I'd still use a protocol to solve this - but you'd have to declare an extension to the NSObject protocol yourself. So in the header for your file you could add something like:
#protocol MyProtocolNSObjectExtras <NSObject>
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
#end
#protocol MyProtocol <NSObjectExtras>
....
#end
Then you get all the same benefits as I described previously.
You need to cast the delegate object to the actual class you are working in order to have the methods. Seen the delegate only can only show the methods defined in the protocol. performSelector:withObject: is not a method defined in the protocol.
[(CLASS*)delegate performSelector:#selector(doStuff:) withObject:myObject afterDelay:2.0];
[NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(doStuff)
userInfo:nil
repeats:NO];
Try that assuming you just want a timer
This could be me doing the design pattern wrong.
I'm implementing asynchronous delegation in an application that uses NSURLConnection. An object wraps the NSURLConnection and handles its delegated messages; that works fine. Now I'm defining my own delegates in the object that uses it (NSURLConnection messages ConnectionWrapper, ConnectionWrapper messages NeedsToUseConnection, you get the idea), and that works as well, however, Xcode emits this warning:
No '-request:finishedWithResult' method found
This is, presumably, because I'm declaring the delegate I'm calling like this:
id<NSObject> delegate;
...and Xcode is checking what NSObject declares in the Foundation framework. My custom delegate message is not there. I am properly insulating the call:
if([delegate respondsToSelector:#selector(request:finishedWithResult:)])
[delegate request:self finishedWithResult:ret];
Aside from turning the warning off -- I like to work with as many warnings on as possible -- is there a way to communicate (either syntactically or via a compiler directive) that I am aware this message is undeclared? Should I, instead, be using an interface design pattern for this รก la Java? Using id<WillReceiveRequestMessages> or something?
Open to suggestion.
A better way of doing it would be to create your own delegate protocol:
#protocol MyControlDelegate <NSObject>
#optional
- (void)request:(MyControl *)request didFinishWithResult:(id)result;
#end
Then, you would declare your delegate like this:
id <MyControlDelegate> delegate;
The compiler will no longer complain when you write this:
if ([delegate respondsToSelector:#selector(request:didFinishWithResult:)])
[delegate request:self didFinishWithResult:result];
The <NSObject> syntax is important in the protocol definition because it tells the compiler to incorporate the NSObject protocol. This is how your protocol gets methods like respondsToSelector:. If you left that out, the compiler would start complaining about respondsToSelector: instead.
This is, presumably, because I'm declaring the delegate I'm calling
like this: ...and Xcode is checking what NSObject declares in the
Foundation framework.
That is incorrect. If that were the case then you would get a warning about the object "may not respond to" the method, or something like that. This is a completely separate problem.
This warning is due to the fact that the compiler must know the signature of a selector in order to call it. This is because, behind the scenes, the compiler translates a method call to either objc_msgSend or objc_msgSend_stret depending on whether the method returns a struct type or not. If it doesn't know the return type, it will guess that it is not a struct, and use the first function. However, this could be wrong.
The solution is to have the method declared anywhere at all. It doesn't even have to be declared in the right class. You can declare it in some dummy protocol that is never used. So long as it is declared somewhere, the compiler will know and will be able to correctly compile it.
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
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).
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:.
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.