Почему вы не должны использовать objc_msgSend() в Objective C?

В Руководстве по времени выполнения Objective C от Apple говорится, что вы никогда не должны использовать objc_msgSend() в своем собственном коде и вместо этого рекомендуем использовать методForSelector:. Однако это не дает никаких оснований для этого.

В чем опасность вызова objc_msgSend() в вашем коде?

Ответ 1

Причина № 1: Плохой стиль - он избыточный и нечитаемый.

Компилятор автоматически генерирует вызовы objc_msgSend() (или их вариант) при обнаружении выражений Objective-C сообщений. Если вы знаете, что класс и селектор должны быть отправлены во время компиляции, нет причин писать

id obj = objc_msgSend(objc_msgSend([NSObject class], @selector(alloc)), @selector(init));

вместо

id obj = [[NSObject alloc] init];

Даже если вы не знаете класс или селектор (или даже оба), он по-прежнему безопаснее (по крайней мере, у компилятора есть шанс предупредить вас, если вы делаете что-то потенциально неприятное/неправильное), чтобы получить правильно напечатанную указатель функции к самой реализации и вместо этого используйте этот указатель функции:

const char *(*fptr)(NSString *, SEL) = [NSString instanceMethodForSelector:@selector(UTF8String)];
const char *cstr = fptr(@"Foo");

Это особенно актуально, когда типы аргументов метода чувствительны к промоакциям по умолчанию - если они есть, то вы не хотите передавать их через переменные аргументы objc_msgSend(), потому что ваша программа будет быстро вызывать undefined.

Причина № 2: опасная и подверженная ошибкам.

Обратите внимание на часть "или какой-либо вариант" в # 1. Не все сообщения отправляются с помощью самой функции objc_msgSend(). Из-за сложностей и требований в ABI (в частности, в вызывающем соглашении функций) существуют отдельные функции для возврата, например, значений или структур с плавающей запятой. Например, в случае метода, который выполняет какой-то поиск (подстроки и т.д.), И возвращает структуру NSRange, в зависимости от платформы, может потребоваться использовать возвращающую структуру версию сообщения функция:

NSRange retval;
objc_msgSend_stret(&retval, @"FooBar", @selector(rangeOfString:), @"Bar");

И если вы ошибаетесь (например, вы используете неподходящую функцию мессенджера, вы смешиваете указатели с возвращаемым значением и self и т.д.), ваша программа, вероятно, будет вести себя неправильно и/или сбой. ( И вы, скорее всего, ошибаетесь,, потому что это даже не так просто - не все методы, возвращающие struct, используют этот вариант, поскольку небольшие структуры будут вписываться в один или два регистра процессора, исключая нужно использовать стек как место возвращаемого значения. Поэтому, если вы не хардкорный хакер ABI, вы скорее хотите, чтобы компилятор выполнял свою работу, или там были драконы.)

Ответ 2

Вы спрашиваете: "Каковы опасности?" и @H2CO3 перечислил некоторые финал с "если вы не хардкор ABI хакер"...

Как и во многих правилах, существуют исключения (и, возможно, еще несколько при ARC). Поэтому ваши аргументы в пользу использования msgSend должны идти примерно так:

[1] Я думаю, что я должен использовать msgSend - не.

[2] Но у меня есть случай здесь... - вы, вероятно, нет, продолжайте искать другое решение.

...

[10] Я действительно думаю, что я должен использовать его здесь - снова подумать.

...

[100] Действительно, это выглядит как случай для msgSend, я не вижу другого решения! ОК, прочитайте Document.m в образце кода TextEdit от Apple. Вы знаете, почему они использовали msgSend? Вы уверены... подумайте еще раз...

...

[1000] Я понимаю, почему Apple использовала его, и мой случай похож... Вы нашли и поняли исключение, которое доказывает правильность и совпадение вашего случая, использовать его!

НТН

Ответ 3

Я могу сделать случай. Мы использовали msgSend в одном из наших файлов C++ (до перехода на ARC) в кроссплатформенном проекте (Windows, Mac и Linux). Мы используем его, чтобы сосчитать ссылку в резервном копировании (общий код), который позже использовался для перехода от внешнего интерфейса к внутреннему и наоборот. По общему признанию, особый случай.