From what I have learned so far: In Objective-C you can send any message to any object. If the object does implement the right method it will be executed otherwise nothing will happen. This is because before the message is sent Objective-C will perform respondsToSelector.
I hope I am right so far.
I did a little program for testing where an action is invoked every time a slider is moved. Also for testing I set the sender to NSButton but in fact it is an NSSlider. Now I asked the object if it will respond to setAlternateTitle. While a NSButton will do and NSSlider will not. If I run the code and do respondsToSelector myself it will tell me the object will not respond to that selector. If I test something else like intValue, it will respond. So my code is fine so far.
- (IBAction)sliderDidMove:(id)sender
{
NSButton *slider = sender;
BOOL responds =
[slider respondsToSelector:#selector(setAlternateTitle)];
if(responds == YES)
{
NSLog(#"YES");
}
else
{
NSLog(#"NO");
}
[slider setAlternateTitle:#"Hello World"];
}
But when I actually send the setAlternateTitle message the program will crash and I am not exactly sure why. Shouldn't it do a respondsToSelector before sending the message?
First of all, the name of a method (its selector) includes all subparts and colon characters, as mvds said.
Second of all, the method -respondsToSelector: is not called by the runtime, it's usually called by the user (yourself or APIs that want to know if a delegate, for example, responds to an optional method of the protocol).
When you send a message to an object, the runtime will look for the implementation of the method in the class of the object (through the object's isa pointer). It's equivalent to sending -respondsToSelector: although the message itself is not dispatched. If the implementation of the method is found in the class or in its superclasses, it's called with all the arguments you passed in.
If not, then the runtime gives the message a second chance to be executed. It will start by sending the message + (BOOL)resolveInstanceMethod:(SEL)name to the class of the object: this method allows you to add the method at runtime to the class: if this message returns YES, it means it can redispatch the message.
If not it gives the message a third chance to be executed, it sends - (id)forwardingTargetForSelector:(SEL)aSelector with the selector, this method can return another object that may be able to respond to the selector on behalf of the actual receiver, if the returned object can respond, the method is executed and the value is returned as if it was returned by the original message. (Note: This is available beginning with OS X 10.6 or iOS 4.)
If the returned object is nil or self (to avoid infinite loops), the runtime gives the message a fourth chance to execute the method… It sends the message - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector to get a method signature in order to build an invocation. If one is provided then an invocation is sent through the message - (void)forwardInvocation:(NSInvocation *)anInvocation. In this method you can parse the invocation and build other messages to send to other targets in any ways you want, and then you can set the return value of the invocation… That value will act as the return value of the original message.
Finally, if no method signature is returned by the object, then the runtime sends the message - (void)doesNotRecognizeSelector:(SEL)aSelector to your object, the implementation of this method in NSObject class throws an exception.
For one thing, the selector is not only the "name" of the message, but also what follows, i.e. the arguments, and their names.
So the correct selector for some -(void)setAlternateTitle:(NSString*)str would be
#selector(setAlternateTitle:)
with the :
As for your problem: If a class respondsToSelector() and you perform that selector, you shouldn't get a crash on sending an unknown selector. What kind of crash log do you see in the debugging window?
(ps. why not include the [slider setAlternateTitle:...] in the if ( responds ) { ... } conditional block?)
"This is because before the message is
sent Objective-C will perform
respondsToSelector."
I guess this is not correct. If the object does not respond to selector, it will crash at runtime. There is no automatic checking by the system. If there was a check by the run time system then we should never get "unrecognized selector sent to instance" exception.
Please make me correct if I am wrong.
EDIT: This is not a straight forward crash, but the default result is the process will be terminated. The whole sequence is already explained in comment and other answer, so I am not going to write that again.
There is an +instancesRespondToSelector: method. As the name suggests, it tells you whether the instances of the class implement that method.
Related
I'm sure I had a tool that could log all system wide notifications but, not being able to find it, I'm writing my own.
So the documentation says I set up the observer by calling:
- (void)addObserver:(id)notificationObserver
selector:(SEL)notificationSelector
name:(NSString *)notificationName
object:(NSString *)notificationSender
…but I don't want to listen to any one notification or object in particular so I set those values to nil. So far so good, I know when notifications are being broadcast.
But how do I get the names of the unknown notification and senders once they've been received? Is it possible?
From the docs:
The method specified by notificationSelector must have one and only one argument (an instance of NSNotification).
Therefore:
-(void)observerMethod:(NSNotification*)notification
{
NSLog( #"%#", notification);
}
The name is a property of the passed notification. The sender is usually the property object. (It is not really the sender, but if somebody else is the sender, the object will be more interesting.) You can retrieve additional information from the userInfo property.
BTW, take care: The selector in this example is observerMethod:, not observerMethod (colon included).
My iPhone app has a login view controller to pop up whenever it is necessary to login. After the user loged in, I have this:
if ([self.presentingViewController respondsToSelector:#selector(userDidLogin)]) {
[((id)self.presentingViewController) userDidLogin];
} else {
[self.presentingViewController dismissModalViewControllerAnimated:YES];
}
However, the compiler kept complaining about "No known instance method for selector userDidLogin". Then I added an instance method named userDidLogin for the login view controller, which was of course not self.presentingViewController, then the build succeeded.
This workaround feels unreasonable to me. Is it a bug in Xcode or intended behavior? Is it is the latter, what is the rationale?
The compiler needs to know the return type of the userDidLogin selector so that it can generate correct code:
If the message returns a struct, the compiler may need to generate a call to objc_msgSend_stret. (Source: Greg Parker's blog.)
If the message returns a floating-point number, the compiler needs (on some platforms) to generate a call to objc_msgSend_fpret. (Source: Greg Parker's blog.)
Otherwise, the compiler needs to generate a call to objc_msgSend.
The userDidLogin selector has no arguments, but if the selector did have arguments, the compiler would also need to know the declared argument types so it could pass the arguments correctly.
Additionally, if you're using ARC, the compiler needs to know the return type and ownership annotations of the selector so that it can generate a release of the return value if appropriate.
The usual way to handle this is just to #import the header file of the class that declares the userDidLogin message. As long as the compiler has seen the selector declared somewhere, it won't complain about sending it to an id.
I'm trying to follow the instructions on the Dropbox developer's site, but I can't figure out how to properly add a DBRestClient object. Right now in my .h file after #end I have:
DBRestClient *restClient;
And in my .m file I have:
- (DBRestClient *)restClient {
if (!restClient) {
restClient =
[[DBRestClient alloc] initWithSession:[DBSession sharedSession]];
restClient.delegate = self;
}
return restClient;
}
This is what the Dropbox page tells me to do, I think; however this results in my app crashing (I think because it tries to release restClient when it shouldn't). I've also tried listing restClient as a nonatomic property but then the uploading, etc methods don't seem to work. (The upload method IS working right now, the app just crashes once it's finished uploading...) Help?
Rupesh, the detailed canonical answer based on bee's comment on Apr 20 is that self was getting deallocated.
this means there must have been code that did the following in order.
a variable created by calling the restClient function that returns an object of type (DBRestClient*)
that variable sets it's component (#property? probably) delegate to self in that function (seen in the
question above)
later, the object self was pointing to was deallocated, meaning delegate was not pointing to anything.
later still, there must have been code that was attempting to dereference delegate for some purpose.
do remember that sending messages to nil objc objects only effectively results in nothing happening or a zero assignment if the message being sent is a function. thus a crash must have been due to some other kind of dereference.
I want to fix warnings in my application code. I have an AddressBookModel.h which implements the TTModel protocol.
You find both interface and implementation of the AdressBookModel in the answer of this question. This is exactly how I implemented it How to use Three20 TTMessageController?
However for
[_delegates perform:#selector(modelDidStartLoad:) withObject:self];
and some other similar selectors I get warnings like
Method -perform:withObject not found (return type defaults to id)
Since _delegates is an array
- (NSMutableArray*)delegates {
if (!_delegates) {
_delegates = TTCreateNonRetainingArray();
}
return _delegates;
}
some suggested to use makeObjectsPerformSelector but this gives me an unrecognized selector sent to instance exception.
Here is the TTModel source code: http://api.three20.info/protocol_t_t_model-p.php
Why is perform:withObject missing? Is performSelector:withObject an alternative (my app crashes using it)?
_delegates is an array of delegates. It is not a true delegate, as signified from the name which is in plural form. An array does not respond to the -modelDidFinishLoad: method — its elements do.
You need to take each element out of the array and call the method of them, e.g.
for (id<TTModelDelegate> delegate in _delegates)
[delegate modelDidFinishLoad:self];
or even easier, using NSArray's -makeObjectsPerformSelector:…:
[_delegates makeObjectsPerformSelector:#selector(modelDidFinishLoad:)
withObject:self];
perform:withObject: method that produces this warning is defined in NSArray(TTCategory) category in NSArrayAdditions.h file in Three20 framework. You need to ensure that this header is imported/referenced properly by compiler, i.e. you need to look at importing this specific header or check your Three20 integration configuration.
You do not need to change this method to makeObjectsPerformSelector: since this is just an import problem (your code runs fine but just produces compile warnings).
Reading between the lines, it looks like you want the objects that are in your _delegates array to all perform a particular selector. You need to call -makeObjectsPerformSelector:withObject: like this:
[_delegates makeObjectsPerformSelector: #selector(modelDidCancelLoad:) withObject: self];
You are mistyping modelDidCancleLoad: should be modelDidCancelLoad:
'NSInvalidArgumentException', reason:
'-[__NSCFArray modelDidCancleLoad:]: unrecognized selector
sent to instance 0x24f480'
Make sure your _delegates is what you are expecting it to be. It seems to be an NSArray.
How would I send a Selector to another class?
I know to send it to a selector in the same file you do
[self performSelector:#selector(doSomething)];
and for sending it to another class I've tried
[otherClass performSelector:#selector(doSomethingElse)];
But I just get an error in the Debugger saying
+[otherClass doSomethingElse]: unrecognized selector sent to class 0xe5c4
Why is this??
EDIT In Response To Daves Answer
' Chances are it's not a class method but rather an instance method … '
How do I create an Instance of My Class then?
From the debug message +[otherClass doSomethingElse] it says that you're sending it to the class itself, which means you're trying to invoke a class (static) method.
Chances are it's not a class method but rather an instance method, which means you should be doing:
[anInstanceOfOtherClass performSelector:#selector(doSomethingElse)];
-(void) removeObserver {…}
There's your problem. The - sign identifies this as an instance method; that is only run on objects of the class. What you need to do is declare and define it as:
+(void)removeObserver …
and you can call this as:
[JGManagedObject removeObserver];
That way, you wont need to use performSelector: to avoid the error message you get when sending an instance message to a class.
To help you along, here's the relevant documentation.