Почему я всегда получаю НЕТ, когда executeSelector: withObject: @YES в iOS, который отличается в macOS? - PullRequest
0 голосов
/ 25 апреля 2018

У меня есть код iOS следующим образом:

//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
    }
}

Я знаю, что не должен писать так. Это неправильно, поскольку @YES является объектом, я должен получить id в качестве аргумента и распаковать его в handler:, например:

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

Как неправильный код, для любого другого объекта любого класса вместо @YES я всегда получаю arg == NO. Проблема в том, почему ON EARTH bool arg всегда NO? Я провел некоторое исследование, и вот что я узнал:

в iOS BOOL на самом деле _Bool (или макрос bool) в C ( _Bool ключевое слово )

в macOS, BOOL на самом деле signed char

Если я создам идентичный код macOS , я получу другой результат, например:

//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!"
    }
}

Это имеет смысл, поскольку BOOL является просто знаковым символом, аргумент приводится из младшего байта @YES адреса объекта. Однако это объяснение не относится к коду iOS. Я думал, что любое ненулевое число будет приведено к истине (и сам адрес должен быть ненулевым). Но почему я получил НЕТ? *

Ответы [ 3 ]

0 голосов
/ 25 апреля 2018

Ответ только на вопрос ..

в iOS, объявление BOOL:

'typedef bool BOOL', и вы также упомянули это ..

такдля iOS это будет принимать значение по умолчанию как ложное.поэтому ваш журнал никогда не будет распечатан.

0 голосов
/ 05 мая 2018

-[NSObject performSelector:withObject:] предполагается использовать только с методом, который принимает ровно один параметр указателя объекта и возвращает указатель объекта.Ваш метод принимает параметр BOOL, а не параметр указателя объекта, поэтому его нельзя использовать с -[NSObject performSelector:withObject:].

Если вы всегда собираетесь отправлять сообщение handler: и знаете, что метод имеетпараметр BOOL, вы должны просто вызвать его напрямую:

[self handler:YES];

Если имя метода будет определяться динамически во время выполнения, но вы знаете, что сигнатура метода всегда будет одинаковой (в этом случаеровно один параметр типа BOOL, ничего не возвращающий), вы можете вызвать его с помощью objc_msgSend().Вы должны привести objc_msgSend к соответствующему типу функции для базовой реализующей функции метода перед вызовом (помните, что реализующая функция для методов Objective C имеет первые два параметра: self и _cmd, за которыми следуетзаявленные параметры).Это связано с тем, что objc_msgSend является батутом, который вызывает соответствующую реализующую функцию со всеми регистрами и стеком, используемыми для хранения неповрежденных аргументов, поэтому вы должны вызывать его с соглашением о вызовах для реализующей функции.В вашем случае вы бы сделали:

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

Кстати, если вы посмотрите на исходный код для -[NSObject performSelector:withObject:], вы увидите, что они делают то же самое -они знают, что сигнатура метода должна быть одним параметром типа id и типом возврата id, поэтому они приводят objc_msgSend к id (*)(id, SEL, id) и затем вызывают его.

В редких случаяхВ случае, когда сигнатура метода также будет динамически изменяться и неизвестна во время компиляции, вам придется использовать NSInvocation.

Давайте рассмотрим, что произошло в вашем случае, когда вы использовали -[NSObject performSelector:withObject:] сметод неправильной подписи.Внутри они вызывают objc_msgSend(), что эквивалентно вызову базовой реализующей функции с указателем функции типа id (*)(id, SEL, id).Однако реализующая функция вашего метода на самом деле имеет тип void (id, SEL, BOOL).Таким образом, вы вызываете функцию, используя указатель на функцию другого типа.Согласно стандарту C (стандарт C99, раздел 6.5.2.2, параграф 9):

Если функция определена с типом, который не совместим с типом (выражения), на который указываетвыражение, которое обозначает вызываемую функцию, поведение не определено.

Итак, в основном вы видите неопределенное поведение .Неопределенное поведение означает, что все может случиться.Как вы видите, он может вернуть одну вещь в одной системе, а другую - в другой, или может привести к сбою всей программы.Вы не можете полагаться на какое-либо конкретное поведение.

0 голосов
/ 25 апреля 2018

Проблема связана с объявлением обработчика. Тип параметра обработчика в этом случае должен быть id ( Objective C ) или Any ( Swift ).

- (void)handler:(id)arg {
    if(arg) {   // Would be same as object passed
         NSLog(@"Uh-hah!");
    }
...