我得到ARC编译器的以下警告:

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

这是我正在做的:

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

为什么我得到这个警告?我理解编译器不能检查选择器是否存在,但为什么会导致泄漏?我怎样才能改变我的代码,让我不再得到这个警告呢?


当前回答

不要压制警告!

有不少于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主线程]。

// 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];

其他回答

如果你不需要传递任何参数,一个简单的解决方法是使用valueForKeyPath。这甚至可以在Class对象上实现。

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

奇怪但事实是:如果可以接受(即result为void并且你不介意让runloop循环一次),添加一个延迟,即使这是零:

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

这删除了警告,大概是因为它向编译器保证没有对象可以返回,并且在某种程度上管理不当。

在项目生成设置中,在其他警告标志(WARNING_CFLAGS)下添加 -Wno-arc-performSelector-leaks

现在只需确保所调用的选择器不会导致对象被保留或复制。

我的猜测是这样的:因为选择器对编译器是未知的,ARC不能强制执行适当的内存管理。

事实上,有时内存管理通过特定的约定与方法的名称绑定在一起。具体来说,我在考虑方便构造函数和make方法;前者按照约定返回一个自动释放的对象;后者是一个保留对象。该约定基于选择器的名称,因此如果编译器不知道选择器,那么它就不能强制执行正确的内存管理规则。

如果这是正确的,我认为你可以安全地使用你的代码,只要你确保内存管理一切正常(例如,你的方法不返回它们分配的对象)。

为了让Scott Thompson的宏更通用:

// 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];
                )