Я получаю следующее предупреждение от компилятора ARC:
"performSelector may cause a leak because its selector is unknown".
Вот что я делаю:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
Почему я получаю это предупреждение? Я понимаю, что компилятор не может проверить, существует ли селектор или нет, но почему это вызвало бы утечку? И как я могу изменить свой код, чтобы я больше не получал это предупреждение?
ios
objective-c
memory-leaks
automatic-ref-counting
Эдуардо Скос
источник
источник
Ответы:
Решение
Компилятор предупреждает об этом по причине. Очень редко это предупреждение просто игнорируют, и его легко обойти. Вот как:
Или более кратко (хотя трудно читать и без охраны):
объяснение
Здесь происходит то, что вы запрашиваете у контроллера указатель на функцию 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 .
Сложный пример
Когда селектор принимает аргументы или возвращает значение, вам придется немного изменить вещи:
Причины для предупреждения
Причина этого предупреждения заключается в том, что в 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
методологию указателя и функции выше. Поскольку потребляемые параметры редко являются проблемой, это вряд ли подойдет.Статические селекторы
Интересно, что компилятор не будет жаловаться на селекторы, объявленные статически:
Причина этого в том, что компилятор фактически может записывать всю информацию о селекторе и объекте во время компиляции. Не нужно делать никаких предположений ни о чем. (Я проверил это год назад, посмотрев на источник, но сейчас у меня нет ссылки.)
подавление
Пытаясь придумать ситуацию, в которой было бы необходимо подавить это предупреждение и разработать хороший код, я отказываюсь. Кто-то, пожалуйста, поделитесь, если у них был опыт, когда было необходимо заставить замолчать это предупреждение (а вышеприведенное не обрабатывает вещи должным образом).
Больше
Можно также создать и
NSMethodInvocation
для этого, но для этого нужно гораздо больше печатать, а также медленнее, поэтому нет особых причин делать это.история
Когда
performSelector:
семейство методов было впервые добавлено в Objective-C, ARC не существовало. При создании ARC Apple решила, что следует сгенерировать предупреждение для этих методов, чтобы направить разработчиков к использованию других средств для явного определения того, как следует обрабатывать память при отправке произвольных сообщений через именованный селектор. В Objective-C разработчики могут сделать это, используя приведения в стиле C к необработанным указателям на функции.С введением Swift, Apple зарегистрировала в
performSelector:
семейство методов , как « по своей сути небезопасным» , и они не доступны для Swift.Со временем мы увидели эту прогрессию:
performSelector:
(ручное управление памятью)performSelector:
performSelector:
и документирует эти методы как «небезопасные»Однако идея отправки сообщений на основе именованного селектора не является «небезопасной». Эта идея долгое время успешно использовалась в Objective-C, а также во многих других языках программирования.
1 Все методы Objective-C имеют две скрытые аргументы,
self
и_cmd
которые неявно добавляются при вызове метода.2 Вызов
NULL
функции небезопасен в C. Охрана, используемая для проверки наличия контроллера, гарантирует, что у нас есть объект. Поэтому мы знаем, что мы получимIMP
отmethodForSelector:
(хотя это может быть_objc_msgForward
, вход в систему пересылки сообщений). По сути, с установленной защитой мы знаем, что у нас есть функция для вызова.3 На самом деле, возможно, что он получит неверную информацию, если объявит ваши объекты как,
id
а вы не импортируете все заголовки. Вы можете столкнуться с ошибками в коде, которые компилятор считает нормальными. Это очень редко, но может случиться. Обычно вы просто получаете предупреждение, что он не знает, какую из двух сигнатур метода выбрать.4 См. Ссылку ARC на сохраненные возвращаемые значения и нераспознанные возвращаемые значения для получения более подробной информации.
источник
performSelector:
методы не реализованы таким образом. Они имеют строгую сигнатуру метода (возвратid
, взятие одного или двухid
с), поэтому не нужно обрабатывать примитивные типы.Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'
при использовании последней версии Xcode. (5.1.1) Тем не менее, я многому научился!void (*func)(id, SEL) = (void *)imp;
не компилируется, я заменил его наvoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
void (*func)(id, SEL) = (void *)imp;
на<…> = (void (*))imp;
или<…> = (void (*) (id, SEL))imp;
В компиляторе LLVM 3.0 в Xcode 4.2 вы можете подавить предупреждение следующим образом:
Если вы получаете ошибку в нескольких местах и хотите использовать систему макросов C, чтобы скрыть прагмы, вы можете определить макрос, чтобы упростить подавление предупреждения:
Вы можете использовать макрос следующим образом:
Если вам нужен результат выполненного сообщения, вы можете сделать это:
источник
pop
иpush
-pragmas гораздо чище и безопаснее.if ([_target respondsToSelector:_selector]) {
аналогичную логику.Я предполагаю, что это так: так как селектор неизвестен компилятору, ARC не может обеспечить надлежащее управление памятью.
Фактически бывают случаи, когда управление памятью связано с именем метода в соответствии с определенным соглашением. В частности, я имею в виду конструкторы удобства по сравнению с методами make ; первый возврат по соглашению автоматически выпущенный объект; последний оставленный объект. Соглашение основано на именах селектора, поэтому, если компилятор не знает селектор, он не может применить правильное правило управления памятью.
Если это правильно, я думаю, что вы можете безопасно использовать свой код, при условии, что вы убедитесь, что все в порядке с управлением памятью (например, что ваши методы не возвращают объекты, которые они выделяют).
источник
__attribute
к каждому методу объяснение управления его памятью. Но это также делает невозможным для обработчика правильно обрабатывать этот шаблон (шаблон, который раньше был очень распространенным, но в последние годы был заменен на более устойчивые шаблоны).SEL
и назначать разные селекторы в зависимости от ситуации? Путь, динамичный язык ...В своем проекте Строительства настройки под другими флагами предупреждения (
WARNING_CFLAGS
), добавить-Wno-arc-performSelector-leaks
Теперь просто убедитесь, что вызываемый вами селектор не вызывает сохранения или копирования вашего объекта.
источник
В качестве обходного пути, пока компилятор не позволит переопределить предупреждение, вы можете использовать среду выполнения
вместо
Вам придется
источник
[_controller performSelector:NSSelectorFromString(@"someMethod")];
иobjc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
не эквивалентны! Взгляните на Несоответствия сигнатур методов и Большой недостаток слабой типизации в Objective-C, они объясняют проблему в деталях.Чтобы игнорировать ошибку только в файле с селектором выполнения, добавьте #pragma следующим образом:
Это будет игнорировать предупреждение в этой строке, но все же разрешить его на протяжении всего вашего проекта.
источник
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
. Я знаю, что если я отключу предупреждение, мне нравится включать его снова в кратчайшие возможные сроки, чтобы я случайно не пропустил другое непредвиденное предупреждение. Вряд ли это проблема, но это просто моя практика, когда я отключаю предупреждение.#pragma clang diagnostic warning push
перед внесением каких-либо изменений и#pragma clang diagnostic warning pop
восстановить предыдущее состояние. Полезно, если вы отключаете нагрузку и не хотите, чтобы в вашем коде было много повторных включений прагматических строк.Странно, но верно: если это приемлемо (т. Е. Результат недействителен и вы не возражаете позволить один раз запустить цикл запуска), добавьте задержку, даже если она равна нулю:
Это удаляет предупреждение, предположительно, потому что заверяет компилятор, что ни один объект не может быть возвращен и каким-то образом неправильно управляется.
источник
Вот обновленный макрос, основанный на ответе, приведенном выше. Это должно позволить вам обернуть ваш код даже с помощью оператора return.
источник
return
не должен быть внутри макроса;return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);
также работает и выглядит более разумным.Этот код не включает флаги компилятора или прямые вызовы времени выполнения:
NSInvocation
позволяет установить несколько аргументов, так что в отличие отperformSelector
этого будет работать с любым методом.источник
Ну, здесь много ответов, но поскольку это немного отличается, объединяя несколько ответов, я думал, что вставлю это. Я использую категорию NSObject, которая проверяет, чтобы убедиться, что селектор возвращает void, а также подавляет компилятор предупреждение.
источник
Ради потомков, я решил бросить свою шляпу в кольцо :)
В последнее время я наблюдаю все больше и больше реструктуризации в сторону от
target
/selector
парадигмы в пользу таких вещей, как протоколы, блоки и т. Д. Однако есть одна замена,performSelector
которую я использовал несколько раз:Похоже, что это чистая, ARC-безопасная и почти идентичная замена
performSelector
без особой надобностиobjc_msgSend()
.Хотя я понятия не имею, есть ли аналог на iOS.
источник
[[UIApplication sharedApplication] sendAction: to: from: forEvent:]
. Однажды я посмотрел на нее, но было бы неудобно использовать связанный с пользовательским интерфейсом класс в середине вашего домена или службы просто для выполнения динамического вызова. Хотя, спасибо, что включили это!id
от-performSelector:...
to:
равен нулю, а это не так. Он просто идет прямо к целевому объекту без предварительной проверки. Так что нет «больше накладных расходов». Это не очень хорошее решение, но причина, которую вы приводите, не является причиной. :)Ответ Мэтта Галлоуэя в этой теме объясняет почему:
Кажется, что обычно безопасно подавить предупреждение, если вы игнорируете возвращаемое значение. Я не уверен, что лучший метод, если вам действительно нужно получить сохраненный объект от executeSelector - кроме «не делай этого».
источник
@ c-road предоставляет правильную ссылку с описанием проблемы здесь . Ниже вы можете увидеть мой пример, когда executeSelector вызывает утечку памяти.
Единственный метод, который вызывает утечку памяти в моем примере, это CopyDummyWithLeak. Причина в том, что ARC не знает, что copySelector возвращает сохраненный объект.
Если вы запустите Memory Leak Tool, вы увидите следующую картинку: ... и нет никаких утечек памяти в любом другом случае:
источник
Чтобы сделать макрос Скотта Томпсона более общим:
Тогда используйте это так:
источник
Не подавляйте предупреждения!
Существует не менее 12 альтернативных решений для работы с компилятором.
В то время как вы были умны во время первой реализации, немногие инженеры на Земле могут последовать вашим шагам, и этот код в конечном итоге сломается.
Безопасные маршруты:
Все эти решения будут работать с некоторой степенью отклонения от вашего первоначального намерения. Предположим, что
param
может быть,nil
если вы этого хотите:Безопасный маршрут, такое же концептуальное поведение:
Безопасный маршрут, немного другое поведение:
(См. Этот ответ)
Используйте любой поток вместо
[NSThread mainThread]
.Опасные маршруты
Требует какого-то глушения компилятора, которое обязательно нарушается. Обратите внимание, что в настоящее время это произошло в Swift .
источник
performSelectorOnMainThread
это не хороший способ , чтобы заставить замолчать предупреждение и имеют побочные эффекты. (это не устраняет утечку памяти). Дополнительный#clang diagnostic ignored
явно подавляет предупреждение очень ясным способом.- (void)
метода - это реальная проблема.Поскольку вы используете ARC, вы должны использовать iOS 4.0 или более позднюю версию. Это означает, что вы можете использовать блоки. Если вместо того, чтобы запоминать селектор для выполнения, вы взяли блок, ARC сможет лучше отследить, что на самом деле происходит, и вам не придется рисковать случайно вызвать утечку памяти.
источник
self
через ivar было предупреждение о компиляторе (например,ivar
вместоself->ivar
).Вместо использования блочного подхода, который доставил мне некоторые проблемы:
Я буду использовать NSInvocation, вот так:
источник
Если вам не нужно передавать какие-либо аргументы, используйте простой обходной путь
valueForKeyPath
. Это даже возможно наClass
объекте.источник
Вы также можете использовать протокол здесь. Итак, создайте протокол так:
В вашем классе, который должен вызвать ваш селектор, у вас есть @property.
Когда вам нужно вызвать
@selector(doSomethingWithObject:)
экземпляр MyObject, сделайте это:источник