ios – 由于其选择器未知,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:,但你也可以class_getMethodImplementation在Objective-C运行时使用(如果你只有一个协议引用,这很有用id<SomeProto>)。这些函数指针被称为IMPs,并且是简单的typedefed函数指针(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时,运行时需要知道如何处理您调用的方法的结果。其结果可能是什么:voidintcharNSString *id,等ARC通常会从您正在使用的对象类型的报头信息。3

实际上ARC只会考虑4个返回值:4

  1. 忽略非对象类型(voidint等)
  2. 保留对象值,然后在不再使用时释放(标准假设)
  3. 不再使用时释放新的对象值(initcopyfamily中的方法或归因于ns_returns_retained
  4. 不要做任何事情,并假设返回的对象值在本地范围内有效(直到内部最多的发布池被耗尽,归因于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的推出,苹果公司已经记录了这个performSelector:方法家族“天生就不安全”,并且它们不适用于Swift。

随着时间的推移,我们看到了这种进展:

  1. 早期版本的Objective-C允许performSelector:(手动内存管理)
  2. Objective-C与ARC警告使用 performSelector:
  3. Swift无法访问performSelector:和记录这些方法为“固有的不安全”

然而,基于命名选择器发送消息的想法并不是“固有的不安全”功能。这个想法在Objective-C以及许多其他编程语言中已经成功使用了很长时间。


1所有的Objective-C方法都有两个隐藏的参数,当你调用一个方法时self_cmd它们被隐式添加。

2调用NULL函数在C中不安全。用于检查控制器是否存在的警卫确保我们有一个对象。因此,我们知道我们会得到一个IMP来自methodForSelector:(尽管可能是_objc_msgForward进入消息转发系统)。基本上,有了守卫,我们知道我们有一个功能可以打电话。

3实际上,如果声明您的对象为,id并且您没有导入所有标题,则可能会得到错误的信息。编译器认为是好的,你可能会在代码中崩溃。这是非常罕见的,但可能发生。通常你会得到一个警告,它不知道两个方法签名中哪一个可以选择。

4有关更多详细信息,请参阅保留返回值和未保留返回值的ARC参考。

添加评论

友情链接:蝴蝶教程