executeSelector может вызвать утечку, потому что его селектор неизвестен

1203

Я получаю следующее предупреждение от компилятора ARC:

"performSelector may cause a leak because its selector is unknown".

Вот что я делаю:

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Почему я получаю это предупреждение? Я понимаю, что компилятор не может проверить, существует ли селектор или нет, но почему это может вызвать утечку? И как я могу изменить свой код, чтобы больше не получать это предупреждение?

  • 1
    Это МОЖЕТ вызвать утечку. Чтобы избежать предупреждения, вы должны передавать / хранить селекторы как строки, за исключением момента, когда вы назначаете его как действие. Если в строке выше вы назначаете его как действие, мне также интересно, почему бы просто не использовать @selector (someMethod :) ??
  • 3
    Имя переменной является динамическим, оно зависит от многих других вещей. Есть риск, что я назову что-то, чего не существует, но это не проблема.
Показать ещё 11 комментариев
Теги:
memory-leaks
automatic-ref-counting

20 ответов

1107
Лучший ответ

Решение

Компилятор предупреждает об этом по какой-либо причине. Очень редко это предупреждение следует просто игнорировать, и его легко обойти. Вот как:

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 или связаны с ns_returns_retained)
  • Ничего не делать и предположить, что значение возвращаемого объекта будет действительным в локальной области (до тех пор, пока внутренний пул релиза не будет исчерпан, отнесен к ns_returns_autoreleased)

Вызов methodForSelector: предполагает, что возвращаемое значение метода, вызываемого им, является объектом, но не сохраняет/освобождает его. Таким образом, вы можете создать утечку, если предполагается, что ваш объект будет выпущен, как в № 3 выше (то есть метод, который вы вызываете, возвращает новый объект).

Для селекторов, которые вы пытаетесь вызвать return void или другие не-объекты, вы можете включить функции компилятора, чтобы игнорировать это предупреждение, но это может быть опасно. Я видел, как Clang просматривает несколько итераций того, как он обрабатывает возвращаемые значения, которые не привязаны к локальным переменным. Нет никаких оснований полагать, что с поддержкой ARC он не может сохранить и освободить значение объекта, возвращаемое из methodForSelector:, даже если вы не хотите его использовать. С точки зрения компилятора, это объект в конце концов. Это означает, что если метод, который вы вызываете, someMethod, возвращает не объект (включая void), вы можете получить значение указателя мусора, которое будет сохранено/освобождено и сработало.

Дополнительные аргументы

Одно из соображений состоит в том, что это будет то же предупреждение с performSelector:withObject:, и вы можете столкнуться с аналогичными проблемами, не объявляя, как этот метод использует параметры. ARC позволяет объявлять потребляемые параметры, и если метод использует этот параметр, вы, вероятно, в конечном итоге отправите сообщение зомби и сбой. Есть способы обойти это с помощью мостового литья, но на самом деле было бы лучше просто использовать методологию IMP и функции указателя функции выше. Поскольку потребляемые параметры редко являются проблемой, это вряд ли возможно.

Статические селектор

Интересно, что компилятор не будет жаловаться на статичные объявления, статически объявленные:

[_controller performSelector:@selector(someMethod)];

Причина этого в том, что компилятор действительно может записывать всю информацию о селекторе и объекте во время компиляции. Не нужно делать никаких предположений ни о чем. (Я проверил это год назад, посмотрев на источник, но сейчас у меня нет ссылки.)

Подавление

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

Подробнее

Можно также создать NSMethodInvocation, чтобы справиться с этим, но для этого требуется намного больше ввода текста, а также медленнее, поэтому нет оснований для этого.

История

Когда семейство методов performSelector: было впервые добавлено в Objective-C, ARC не существовало. При создании ARC Apple решила, что для этих методов должно быть создано предупреждение, как способ направить разработчиков на использование других средств для явного определения того, как память должна обрабатываться при отправке произвольных сообщений с помощью именованного селектора. В Objective-C разработчики могут это сделать, используя приведения стиля C к указателям на raw-указатели.

С введением 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 на сохраненные возвращаемые значения и недопустимые значения возврата для более подробной информации.

  • 0
    @wbyoung Если ваш код решает проблему сохранения, мне интересно, почему performSelector: методы не реализованы таким образом. Они имеют строгую сигнатуру метода (возвращая id , принимая один или два id ), поэтому не нужно обрабатывать примитивные типы.
  • 0
    @wbyoung С MRC ты тоже этого не знал. Возвращает ли (динамический) селектор сохраненный объект или даже использует аргумент. Я использую селекторы только для target + action pattern и никогда не передаю туда copy или alloc , поэтому я замалчиваю это предупреждение глобально, а не делаю прагма push / pop.
Показать ещё 26 комментариев
1102

В компиляторе LLVM 3.0 в Xcode 4.2 вы можете подавить предупреждение следующим образом:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

Если вы получаете ошибку в нескольких местах и ​​хотите использовать макросеть C, чтобы скрыть прагмы, вы можете определить макрос, чтобы упростить подавление предупреждения:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

Вы можете использовать макрос следующим образом:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

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

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);
  • 0
    Этот метод может вызвать утечку памяти, если для оптимизации задано что-либо кроме None.
  • 4
    @Eric Нет, это невозможно, если только вы не вызываете забавные методы, такие как «initSomething», «newSomething» или «thingCopy ».
Показать ещё 8 комментариев
215

Мое предположение об этом заключается в следующем: поскольку селектор неизвестен компилятору, ARC не может обеспечить правильное управление памятью.

Фактически, времена, когда управление памятью привязано к имени метода по определенному соглашению. В частности, я думаю о конструкторах удобства по сравнению с методами make; первые возвращают по соглашению автореализованный объект; последний - сохраненный объект. Соглашение основано на именах селектора, поэтому, если компилятор не знает селектор, тогда он не может обеспечить правильное правильное управление памятью.

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

  • 5
    Спасибо за ответ, я посмотрю больше на это, чтобы увидеть, что происходит. Любая идея о том, как я могу обойти предупреждение и заставить его исчезнуть? Я бы не хотел, чтобы предупреждение всегда оставалось в моем коде для безопасного вызова.
  • 1
    performSelector : как насчет обертки performSelector который получает строку (имя метода) и вызывает исходный performSelector путем индексации в массиве объектов SEL , которые известны компилятору (или, возможно, переключаются между этими SEL)? немного неуклюжий, может быть, но должен работать ...
Показать ещё 12 комментариев
114

В вашем проекте "Настройки сборки" в разделе "Другие флаги предупреждения" (WARNING_CFLAGS) добавьте -Wno-arc-performSelector-leaks

Теперь просто убедитесь, что вызываемый вами селектор не приводит к тому, что ваш объект будет сохранен или скопирован.

  • 12
    Обратите внимание, что вы можете добавить один и тот же флаг для определенных файлов, а не для всего проекта. Если вы посмотрите под Build Phases-> Compile Sources, вы можете установить для каждого файла флаги компилятора (так же, как вы хотите сделать для исключения файлов из ARC). В моем проекте только один файл должен использовать селекторы таким образом, поэтому я просто исключил его и оставил остальные.
109

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

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

вместо

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Вам нужно

#import <objc/message.h>
  • 8
    ARC распознает соглашения по какао, а затем добавляет удержания и выпуски на основе этих соглашений. Поскольку C не следует этим соглашениям, ARC заставляет вас использовать методы ручного управления памятью. Если вы создаете объект CF, вы должны CFRelease () его. Если вы dispatch_queue_create (), вы должны dispatch_release (). В итоге, если вы хотите избежать предупреждений ARC, вы можете избежать их, используя объекты C и ручное управление памятью. Кроме того, вы можете отключить ARC для каждого файла, используя флаг компилятора -fno-objc-arc в этом файле.
  • 8
    Не без кастинга, ты не можешь. Varargs - это не то же самое, что явно заданный список аргументов. Обычно это работает по совпадению, но я не считаю «по совпадению» правильным.
Показать ещё 2 комментария
83

Чтобы игнорировать ошибку только в файле с помощью селектора параметров, добавьте #pragma следующим образом:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

Это будет игнорировать предупреждение в этой строке, но все же разрешить его на протяжении всего вашего проекта.

  • 6
    Я понял, что вы также можете снова включить предупреждение сразу после рассматриваемого метода с помощью #pragma clang diagnostic warning "-Warc-performSelector-leaks" . Я знаю, что если я отключу предупреждение, мне нравится включать его снова в кратчайшие возможные сроки, чтобы я случайно не пропустил другое непредвиденное предупреждение. Вряд ли это проблема, но это просто моя практика, когда я отключаю предупреждение.
  • 2
    Вы также можете восстановить предыдущее состояние конфигурации компилятора, используя #pragma clang diagnostic warning push перед тем, как вносить какие-либо изменения, и #pragma clang diagnostic warning pop для восстановления предыдущего состояния. Полезно, если вы отключаете нагрузку и не хотите, чтобы в вашем коде было много повторных включений прагматических строк.
Показать ещё 1 комментарий
63

Странно, но верно: если это приемлемо (т.е. результат недействителен, и вы не возражаете, чтобы один раз запустил цикл runloop), добавьте задержку, даже если это ноль:

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

Это устраняет предупреждение, по-видимому, потому, что оно заверяет компилятор, что ни один объект не может быть возвращен и каким-то образом запутан.

  • 1
    Знаете ли вы, действительно ли это решает связанные проблемы управления памятью, или у него те же проблемы, но XCode не достаточно умен, чтобы предупредить вас этим кодом?
  • 0
    Это семантически не то же самое! Использование executeSelector: withObject: AfterDelay: выполнит селектор при следующем запуске цикла запуска. Следовательно, этот метод возвращается немедленно.
Показать ещё 1 комментарий
33

Вот обновленный макрос, основанный на ответе, приведенном выше. Это должно позволить вам обернуть ваш код даже с помощью оператора return.

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);
  • 4
    return не должен быть внутри макроса; return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]); также работает и выглядит более разумным.
30

Этот код не включает флаги компилятора или прямые вызовы во время выполнения:

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocation позволяет установить несколько аргументов таким образом, чтобы в отличие от performSelector это будет работать с любым методом.

  • 3
    Знаете ли вы, действительно ли это решает связанные проблемы управления памятью, или у него те же проблемы, но XCode не достаточно умен, чтобы предупредить вас этим кодом?
  • 1
    Можно сказать, что это решает проблемы управления памятью; но это потому, что это в основном позволяет вам указать поведение. Например, вы можете разрешить вызову сохранять аргументы или нет. Насколько мне известно, он пытается исправить проблемы с несоответствием сигнатур, которые могут возникнуть, полагая, что вы знаете, что делаете, и не предоставляете ему неверные данные. Я не уверен, что все проверки могут быть выполнены во время выполнения. Как упоминается в другом комментарии, mikeash.com/pyblog/… хорошо объясняет, что могут сделать несоответствия.
19

Ну, здесь много ответов, но, поскольку это немного отличается, объединив несколько ответов, я думал, что я их вложу. Я использую категорию NSObject, которая проверяет, чтобы селектор вернул пустоту, а также подавляет предупреждение компилятора.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end
  • 0
    Должен ли 'v' быть заменен на _C_VOID? _C_VOID объявлен в <objc / runtime.h>.
  • 1
    Звучит хорошо. Я больше ничего не делаю с Objective-C ...
16

Для потомков я решил бросить свою шляпу в кольцо:)

Недавно я видел все больше и больше реструктуризации от парадигмы target/selector, в пользу таких вещей, как протоколы, блоки и т.д. Однако есть одна замена для performSelector что я использовал несколько раз сейчас:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

Кажется, что это чистая, ARC-безопасная и почти идентичная замена для performSelector, не имея большого значения с objc_msgSend().

Хотя, я понятия не имею, есть ли аналог, доступный на iOS.

  • 6
    Спасибо, что [[UIApplication sharedApplication] sendAction: to: from: forEvent:] это. Оно доступно в iOS: [[UIApplication sharedApplication] sendAction: to: from: forEvent:] . Однажды я посмотрел на нее, но было бы неудобно использовать связанный с пользовательским интерфейсом класс в середине вашего домена или службы просто для выполнения динамического вызова. Хотя, спасибо, что включили это!
  • 2
    Еа! У него будет больше накладных расходов (поскольку он должен проверить, доступен ли метод и пройтись по цепочке респондента, если он не существует), и иметь другое поведение при ошибках (пройтись по цепочке респондента и вернуть NO, если он не может ничего найти который отвечает на метод, а не просто сбой). Это также не работает, когда вы хотите id из -performSelector:...
Показать ещё 1 комментарий
15

Ответ Matt Galloway на этот поток объясняет, почему:

Рассмотрим следующее:

id anotherObject1 = [someObject performSelector:@selector(copy)];
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];

Теперь, как ARC может знать, что первый возвращает объект с сохранением числа 1, а второй возвращает объект, который автореализован?

Кажется, что безопасно подавлять предупреждение, если вы игнорируете возвращаемое значение. Я не уверен, что лучше всего, если вам действительно нужно получить сохраненный объект из performSelector - кроме "не делайте этого".

14

@c-road предоставляет правильную ссылку с описанием проблемы здесь. Ниже вы можете увидеть мой пример, когда performSelector вызывает утечку памяти.

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}

Единственным методом, который вызывает утечку памяти в моем примере, является CopyDummyWithLeak. Причина в том, что ARC не знает, что copySelector возвращает сохраненный объект.

Если вы запустите инструмент Memory Leak Tool, вы увидите следующее изображение: Изображение 5170 ... и нет утечек памяти в любом другом случае: Изображение 5171

5

Сделать макрос Скотта Томсона более универсальным:

// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)

#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")

Затем используйте его следующим образом:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )
  • 0
    FWIW, я не добавил макрос. Кто-то добавил это к моему ответу. Лично я бы не использовал макрос. Прагма предназначена для работы с особым случаем в коде, и прагмы очень четкие и прямые о том, что происходит. Я предпочитаю держать их на месте, а не прятать или абстрагировать их от макроса, но это только я. YMMV.
  • 0
    @ ScottThompson Это честно. Для меня легко найти этот макрос по всей моей кодовой базе, и я, как правило, также добавляю предупреждение без вывода сообщений для решения основной проблемы.
4

Поскольку вы используете ARC, вы должны использовать iOS 4.0 или новее. Это означает, что вы можете использовать блоки. Если вместо того, чтобы помнить, что селектор выполнил вас, вместо этого взял блок, ARC сможет лучше отслеживать, что происходит на самом деле, и вам не придется рисковать случайно ввести утечку памяти.

  • 0
    На самом деле, блоки позволяют очень легко случайно создать цикл сохранения, который ARC не решает. Я все еще хотел бы, чтобы было предупреждение компилятора, когда вы неявно использовали self через ivar (например, ivar вместо self->ivar ).
  • 0
    Ты имеешь в виду как -Wimplicit-retain-self?
3

Не подавлять предупреждения!

Существует не менее 12 альтернативных решений для возиться с компилятором.
В то время как вы умны в то время, когда первая реализация, несколько инженеров на Земле могут следовать вашим шагам, и этот код в конечном итоге сломается.

Безопасные маршруты:

Все эти решения будут работать с некоторой степенью вариации от вашего первоначального намерения. Предположим, что param может быть nil, если вы этого желаете:

Безопасный маршрут, такое же концептуальное поведение:

// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

Безопасный маршрут, несколько другое поведение:

(см. этот ответ)
Используйте любой поток вместо [NSThread mainThread].

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

Опасные маршруты

Требуется какое-то заглушение компилятора, которое обязательно сломается. Обратите внимание, что в настоящее время сделал разрыв Swift.

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];
  • 2
    Формулировка очень неправильная. Безопасные маршруты вовсе не безопаснее, чем опасны. Это, возможно, более опасно, потому что оно скрывает предупреждение неявно.
  • 0
    Я исправлю формулировку, чтобы не оскорблять, но я верю своему слову. Единственное время, когда я нахожу предупреждение о глушении, это если я не владею кодом. Ни один инженер не может безопасно поддерживать код без вывода сообщений, не понимая всех последствий, которые могут означать чтение этого аргумента, и эта практика является довольно рискованной; особенно если вы рассматриваете 12, простой английский, надежные альтернативы.
Показать ещё 4 комментария
2

Вместо использования блочного подхода, который дал мне некоторые проблемы:

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

Я буду использовать NSInvocation, например:

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }
1

Если вам не нужно передавать какие-либо аргументы, легко использовать valueForKeyPath. Это возможно даже для объекта Class.

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}
-1

Есть еще один способ обойти это предупреждение.

<del>[instanceSelector performSelector:stopSelector];</del>

Использовать метод overDelay overloaded

[instanceSelector performSelector:stopSelector withObject:self afterDelay:0.0];
-2

Здесь вы также можете использовать протокол. Итак, создайте такой протокол:

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

В вашем классе, который должен вызвать ваш селектор, вы должны иметь @property.

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

Когда вам нужно вызвать @selector(doSomethingWithObject:) в экземпляре MyObject, сделайте следующее:

[self.source doSomethingWithObject:object];
  • 2
    Привет, Ву, спасибо, но смысл использования NSSelectorFromString в том, что вы не знаете, какой селектор вы хотите вызвать во время выполнения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню