Решение
Компилятор предупреждает об этом по причине. Очень редко это предупреждение просто игнорируют, и его легко обойти. Вот как:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
Или более кратко (хотя трудно читать и без охраны):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
Объяснение
Здесь происходит то, что вы запрашиваете у контроллера указатель на функцию C для метода, соответствующего контроллеру. Все NSObject
отвечают на methodForSelector:
, но вы также можете использовать class_getMethodImplementation
во время выполнения Objective-C (полезно, если у вас есть только ссылка на протокол, например id<SomeProto>
). Эти указатели функций называются IMP
s и являются простыми typedef
ed указателями на функции (id (*IMP)(id, SEL, ...)
) 1 . Это может быть близко к фактической сигнатуре метода, но не всегда точно совпадает.
Когда у вас есть IMP
, вам нужно привести его к указателю функции, который включает в себя все детали, которые нужны ARC (включая два неявных скрытых аргумента self
и _cmd
каждого вызова метода Objective-C ). Это обрабатывается в третьей строке ((void *)
в правой части просто говорит компилятору, что вы знаете, что делаете, а не генерировать предупреждение, поскольку типы указателей не совпадают).
Наконец, вы вызываете указатель на функцию 2 .
Сложный пример
Когда селектор принимает аргументы или возвращает значение, вам придется немного изменить вещи:
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;
Основание для предупреждения
Причина этого предупреждения в том, что в ARC среде выполнения необходимо знать, что делать с результатом вызываемого вами метода. Результат может быть любым: void
, int
, char
, NSString *
, id
и т. Д. ARC обычно получает эту информацию из заголовка типа объекта, с которым вы работаете. 3
На самом деле ARC рассматривает только 4 вещи для возвращаемого значения: 4
- Игнорировать необъектные типы (
void
, int
и т. Д.)
- Сохранить значение объекта, затем сбросить, когда он больше не используется (стандартное предположение)
- Выпускать новые значения объектов, когда они больше не используются (методы в семействе
init
/ copy
или приписываются ns_returns_retained
)
- Ничего не делать и предполагать, что возвращаемое значение объекта будет действительным в локальной области (до тех пор, пока не будет опустошен внутренний пул релизов, приписывается
ns_returns_autoreleased
)
Вызов methodForSelector:
предполагает, что возвращаемое значение метода, который он вызывает, является объектом, но не сохраняет / не освобождает его. Таким образом, вы можете в конечном итоге создать утечку, если ваш объект должен быть освобожден, как в # 3 выше (то есть вызываемый вами метод возвращает новый объект).
Для селекторов, которые вы пытаетесь вызвать этот возврат void
или другие не-объекты, вы можете включить функции компилятора, чтобы игнорировать предупреждение, но это может быть опасно. Я видел, как Clang прошел несколько итераций того, как он обрабатывает возвращаемые значения, которые не назначены локальным переменным. Нет никакой причины, по которой при включенном ARC он не может сохранять и освобождать значение объекта, возвращаемое из methodForSelector:
, даже если вы не хотите его использовать. С точки зрения компилятора, это все-таки объект. Это означает, что если вызываемый вами метод, someMethod
, возвращает необъект (включая void
), вы можете получить значение указателя мусора, которое будет сохранено / освобождено, и произойдет сбой.
Дополнительные аргументы
Одним из соображений является то, что это то же самое предупреждение будет появляться с performSelector:withObject:
, и вы можете столкнуться с подобными проблемами, не заявив, как этот метод потребляет параметры. ARC позволяет объявлять использованные параметры , и если метод использует параметр, вы, вероятно, в конечном итоге отправите сообщение зомби и произойдет сбой. Есть способы обойти это с помощью мостового приведения, но на самом деле было бы лучше просто использовать методологию IMP
и указатель на функцию выше. Так как потребляемые параметры редко являются проблемой, это вряд ли возникнет.
Статические селекторы
Интересно, что компилятор не будет жаловаться на селекторы, объявленные статически:
[_controller performSelector:@selector(someMethod)];
Причина этого в том, что компилятор фактически может записывать всю информацию о селекторе и объекте во время компиляции.Не нужно делать никаких предположений ни о чем.(Я проверил это год назад, посмотрев на источник, но сейчас у меня нет ссылки.)
Подавление
В попытке придумать ситуацию, когда это подавлениебыло бы необходимо предупреждение и хороший дизайн кода, я выхожу пустым.Кто-то, пожалуйста, поделитесь, если у них был опыт, когда было необходимо заставить замолчать это предупреждение (и вышеупомянутое не обрабатывает вещи должным образом).
Больше
Можно создать NSMethodInvocation
длясправиться и с этим, но это требует гораздо большего набора текста, а также медленнее, поэтому нет особых причин делать это.
History
Когда впервые было добавлено семейство методов performSelector:
до Objective-C ARC не существовало.При создании ARC Apple решила, что следует сгенерировать предупреждение для этих методов, чтобы направлять разработчиков к использованию других средств для явного определения того, как следует обрабатывать память при отправке произвольных сообщений через именованный селектор.В Objective-C разработчики могут сделать это, используя приведения в стиле C. к необработанным указателям на функции.
С введением Swift Apple задокументировала семейство performSelector:
методов как«изначально небезопасные», и они не доступны для Swift.
Со временем мы наблюдаем эту прогрессию:
- Ранние версии Objective-C позволяют
performSelector:
(ручное управление памятью) - Objective-C с ARC предупреждает об использовании
performSelector:
- Swift не имеет доступа к
performSelector:
и документирует эти методы как «изначально небезопасные»
Однако идея отправки сообщений на основе именованного селектора не является «небезопасной».Эта идея давно и успешно используется в Objective-C и многих других языках программирования.
1 Все методы Objective-C имеют два скрытых аргумента, self
и _cmd
, которые неявно добавляются при вызове метода.
2 Вызов функции NULL
небезопасен в C. Охрана, используемая для проверки наличияКонтроллер гарантирует, что у нас есть объект.Поэтому мы знаем, что получим IMP
от methodForSelector:
(хотя это может быть _objc_msgForward
, вход в систему пересылки сообщений).По сути, с установленной защитой мы знаем, что у нас есть функция для вызова.
3 На самом деле, она может получить неверную информацию, если объявит ваши объекты как id
иВы не импортируете все заголовки.Вы можете столкнуться с ошибками в коде, которые компилятор считает нормальными.Это очень редко, но может случиться.Обычно вы просто получаете предупреждение, что он не знает, какую из двух сигнатур метода выбрать.
4 См. Ссылку ARC на сохраненные возвращаемые значения и нераспределенные возвращаемые значения для более подробной информации.