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的引入,Apple 已将这一performSelector:系列方法记录为“本质上不安全”,并且它们不适用于Swift。

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

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

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


1所有Objective-C方法都有两个隐藏的参数,self并且_cmd在调用方法时会隐式添加这些参数。

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

3实际上,如果将对象声明为id并且您没有导入所有标题,则它可能会获取错误的信息。你最终可能会遇到编译器认为没问题的代码崩溃。这种情况非常罕见,但可能会发生。通常,您只会收到一条警告,表示它不知道可以选择哪两种方法签名。

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

添加评论

友情链接:蝴蝶教程