Решение
Компилятор предупреждает об этом по причине. Очень редко это предупреждение просто игнорируют, и его легко обойти. Вот как:
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
На самом деле есть только 4 вещи, которые ARC будет учитывать для возвращаемого значения: 4
- Типы игнорировать не-объект (
void
, int
и т.д.)
- Сохраните значение объекта, затем отпустите, когда он больше не используется (стандартное предположение)
- Выпускать новые значения объекта, когда они больше не используются (методы в
init
/ copy
family или приписываются с ns_returns_retained
)
- Ничего не делать и предполагать, что возвращаемое значение объекта будет действительным в локальной области (до тех пор, пока внутренняя большая часть пула релизов не будет слита
ns_returns_autoreleased
).
Вызов methodForSelector:
предполагает, что возвращаемое значение метода, который он вызывает, является объектом, но не сохраняет / освобождает его. Таким образом, вы можете создать утечку, если ваш объект должен быть освобожден, как в # 3 выше (то есть вызываемый вами метод возвращает новый объект).
Для селекторов, которые вы пытаетесь вызвать этот возврат void
или другие не-объекты, вы можете включить функции компилятора, чтобы игнорировать предупреждение, но это может быть опасно. Я видел, как Clang прошел несколько итераций того, как он обрабатывает возвращаемые значения, которые не назначены локальным переменным. Нет никакой причины, по которой при включенном ARC он не может сохранять и освобождать возвращаемое значение объекта, methodForSelector:
даже если вы не хотите его использовать. С точки зрения компилятора, это все-таки объект. Это означает, что если метод, который вы вызываете, someMethod
возвращает не объект (в том числе void
), вы можете получить значение-указатель мусора при сохранении / отпускании и сбой.
Дополнительные аргументы
Одно из соображений заключается в том, что это то же самое предупреждение, с которым performSelector:withObject:
вы можете столкнуться, и вы можете столкнуться с подобными проблемами, не заявив, как этот метод потребляет параметры. ARC позволяет объявлять использованные параметры , и если метод использует параметр, вы, вероятно, в конечном итоге отправите сообщение зомби и произойдет сбой. Есть способы обойти это с помощью мостового приведения, но на самом деле было бы лучше просто использовать IMP
методологию указателя и функции выше. Поскольку потребляемые параметры редко являются проблемой, это вряд ли подойдет.
Статические селекторы
Интересно, что компилятор не будет жаловаться на селекторы, объявленные статически:
[_controller performSelector:@selector(someMethod)];
Причина этого в том, что компилятор фактически может записывать всю информацию о селекторе и объекте во время компиляции. Не нужно делать никаких предположений ни о чем. (Я проверил это год назад, посмотрев на источник, но сейчас у меня нет ссылки.)
подавление
Пытаясь придумать ситуацию, в которой было бы необходимо подавить это предупреждение и разработать хороший код, я отказываюсь. Кто-то, пожалуйста, поделитесь, если у них был опыт, когда было необходимо заставить замолчать это предупреждение (а вышеприведенное не обрабатывает вещи должным образом).
Больше
Можно также создать и NSMethodInvocation
для этого, но для этого нужно гораздо больше печатать, а также медленнее, поэтому нет особых причин делать это.
история
Когда 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 на сохраненные возвращаемые значения и нераспознанные возвращаемые значения для получения более подробной информации.