iosmacosbooleanperformselector

Why I always get NO when performSelector:withObject:@YES in iOS, which is different in macOS?


I have some iOS code as follows:

//iOS
- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(handler:) withObject:@YES];
}

- (void)handler:(BOOL)arg {  //always NO
    if(arg) {  
         NSLog(@"Uh-hah!");   //won't log
    }
}

I know I shouldn't write like this. It's wrong since @YES is an object, I should receive an id as argument and unbox it in handler:, like:

- (void)handler:(id)arg {
    if([arg boolValue]) {...}
}

As a wrong code, for any other object of whatever class instead of @YES, I always get arg == NO.The problem is, why ON EARTH bool arg is always NO? I did some research and here is what I've learned:

in iOS, BOOL is actually _Bool(or macro bool) in C (_Bool keyword)

in macOS, BOOL is actually signed char

If I create an identical macOS code, I'll get different result, like:

//macOS
- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(handler:) withObject:@YES];  //@YES's address: say 0x00007fffa38533e8
}

- (void)handler:(BOOL)arg {  //\xe8 (=-24)
    if(arg) {
         NSLog(@"Uh-hah!");  //"Uh-huh!"
    }
}

It makes sense since BOOL is just signed char, the argument is cast from the lowest byte of @YES object's address. However, this explanation won't apply to iOS code. I thought any non-zero number would be cast to true(and the address itself must be non-zero).Bu why I got NO? *


Solution

  • -[NSObject performSelector:withObject:] is only supposed to be used with a method that takes exactly one object-pointer parameter, and returns an object-pointer. Your method takes a BOOL parameter, not an object-pointer parameter, so it cannot be used with -[NSObject performSelector:withObject:].

    If you are always going to send the message handler: and you know the method has a BOOL parameter, you should just call it directly:

    [self handler:YES];
    

    If the name of the method will be determined dynamically at runtime, but you know the signature of the method will always be the same (in this case exactly one parameter of type BOOL, returning nothing), you can call it with objc_msgSend(). You must cast objc_msgSend to the appropriate function type for the underlying implementing function of the method before calling it (remember, the implementing function for Objective-C methods have the first two parameters being self and _cmd, followed by the declared parameters). This is because objc_msgSend is a trampoline that calls into the appropriate implementing function with all the registers and stack used for storing the arguments intact, so you must call it with the calling convention for the implementing function. In your case, you would do:

    SEL selector = @selector(handler:); // assume this is computed dynamically at runtime
    ((void (*)(id, SEL, BOOL))objc_msgSend)(self, selector, YES);
    

    By the way, if you look at the source code for -[NSObject performSelector:withObject:], you will see that they do the same thing -- they know that the signature of the method must be one parameter of type id and a return type of id, so they cast objc_msgSend to id (*)(id, SEL, id) and then call it.

    In the rare case where the signature of the method will also vary dynamically and is not known at compile-time, then you will have to use NSInvocation.

    Let's consider what happened in your case when you used -[NSObject performSelector:withObject:] with a method of the wrong signature. Inside, they call objc_msgSend(), which is equivalent to calling the underlying implementing function, with a function pointer of the type id (*)(id, SEL, id). However, the implementing function of your method actually has type void (id, SEL, BOOL). So you are calling a function using a function pointer of a different type. According to the C standard (C99 standard, section 6.5.2.2, paragraph 9):

    If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.

    So basically what you are seeing is undefined behavior. Undefined behavior means anything can happen. It could return one thing on one system and another thing on another, as you're seeing, or it could crash the whole program. You can't rely on any specific behavior.