由于其选择器未知,performSelector可能会导致泄漏
我收到ARC编译器的以下警告:
"performSelector may cause a leak because its selector is unknown".
以下是我正在做的事情:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
为什么我会收到此警告? 我知道编译器不能检查选择器是否存在,但为什么会导致泄漏? 我怎样才能更改我的代码,以便我不再收到此警告?
解
编译器出于某种原因警告这个问题。 这种警告很容易被忽略,这很容易解决。 就是这样:
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:
响应,但是你也可以在Objective-C运行库中使用class_getMethodImplementation
(如果你只有一个协议引用,比如id<SomeProto>
),那么它很有用。 这些函数指针被称为IMP
,并且是简单的typedef
函数指针( id (*IMP)(id, SEL, ...)
)1。 这可能接近方法的实际方法签名,但并不总是完全匹配。
一旦你拥有了IMP
,你需要将它转换成一个函数指针,该指针包含ARC需要的所有细节(包括每个Objective-C方法调用的隐含的隐含参数self
和_cmd
)。 这是在第三行中处理的(void *)
右边的(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通常从你正在处理的对象类型的头部获取这些信息。
实际上ARC只会考虑4个返回值:4
void
, int
等) init
/ copy
系列中的方法或由ns_returns_retained
) ns_returns_autoreleased
) 对methodForSelector:
的调用methodForSelector:
假定它所调用的方法的返回值是一个对象,但不保留/释放它。 所以,如果你的对象应该像上面#3那样被释放(也就是说,你调用的方法返回一个新对象),你最终可能会创建一个泄漏。
对于试图调用返回void
或其他非对象的选择器,可以启用编译器功能来忽略该警告,但这可能很危险。 我已经看到Clang经历了几次迭代,它处理了未分配给局部变量的返回值。 没有理由在启用ARC的情况下无法保留并释放methodForSelector:
返回的对象值methodForSelector:
即使您不想使用它。 从编译器的角度来看,它毕竟是一个对象。 这意味着如果您调用的方法someMethod
正在返回一个非对象(包括void
),则最终可能会保留/释放垃圾指针值并导致崩溃。
其他参数
一个考虑因素是,这与performSelector:withObject:
会发生相同的警告performSelector:withObject:
并且您可能会遇到类似的问题,而不会声明该方法如何消耗参数。 ARC允许声明消耗的参数,并且如果该方法使用该参数,则最终可能会向僵尸发送消息并崩溃。 有很多方法可以通过桥接模式来解决这个问题,但是真的使用上面的IMP
和函数指针方法会更好。 由于消耗的参数很少成为问题,所以不太可能出现。
静态选择器
有趣的是,编译器不会抱怨静态声明的选择器:
[_controller performSelector:@selector(someMethod)];
原因是编译器实际上能够在编译期间记录有关选择器和对象的所有信息。 它不需要对任何事情做任何假设。 (我通过查看源代码在一年前检查过这一年,但现在没有参考。)
抑制
在试图想要抑制这种警告是必要的并且良好的代码设计的时候,我会空白。 有人请分享,如果他们有经验沉默这个警告是必要的(和上述不正确处理的事情)。
更多
也可以建立一个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在C中调用一个NULL
函数是不安全的。用于检查控制器是否存在的警卫确保我们有一个对象。 因此我们知道我们将从methodForSelector:
获得一个IMP
(尽管它可能是_objc_msgForward
,进入消息转发系统)。 基本上,有了守卫,我们知道我们有一个功能可以打电话。
3实际上,如果将对象声明为id
并且您没有导入所有标头,则可能会得到错误的信息。 编译器认为是好的,你可能会在代码中崩溃。 这是非常罕见的,但可能发生。 通常你会得到一个警告,它不知道两个方法签名中哪一个可以选择。
4有关更多详细信息,请参阅保留返回值和未保留返回值的ARC参考。
在Xcode 4.2中的LLVM 3.0编译器中,您可以按如下方式取消警告:
#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]
);
我对此的猜测是:由于编译器不知道选择器,因此ARC无法执行正确的内存管理。
实际上,有些时候内存管理通过特定的约定与方法的名称相关联。 具体来说,我正在考虑便捷的构造函数与make方法; 前者按惯例返回自动放弃的对象; 后者是保留的对象。 约定基于选择器的名称,所以如果编译器不知道选择器,那么它不能执行正确的内存管理规则。
如果这是正确的,我认为你可以安全地使用你的代码,只要你确保在内存管理方面一切正常(例如,你的方法不返回它们分配的对象)。
链接地址: http://www.djcxy.com/p/3357.html上一篇: performSelector may cause a leak because its selector is unknown