performSelector可能会导致泄漏,因为其选择器未知

爱德华多·斯科兹(Eduardo Scoz)

ARC编译器收到以下警告:

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

这是我在做什么:

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

为什么会收到此警告?我知道编译器无法检查选择器是否存在,但是为什么会导致泄漏?以及如何更改我的代码,以便不再收到此警告?

by

编译器出于某种原因对此发出警告。很少会忽略此警告,并且很容易解决。这是如何做:

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. 不再使用时释放新的对象值(init/copy系列中的方法或由赋予的方法ns_returns_retained
  4. 不执行任何操作并假定返回的对象值将在本地范围内有效(直到耗尽最内部的释放池,并归因于ns_returns_autoreleased

对的调用methodForSelector:假定其所调用方法的返回值是一个对象,但不保留/释放该对象。因此,如果应该按照上面的#3释放对象,那么最终可能会导致泄漏(也就是说,您正在调用的方法将返回一个新对象)。

对于要尝试调用该返回值void或其他非对象的选择器,可以使编译器功能忽略警告,但这可能很危险。我已经看到Clang对它如何处理未分配给局部变量的返回值进行了几次迭代。启用ARC并没有理由methodForSelector:即使您不想使用它也无法保留和释放从中返回的对象值从编译器的角度来看,它毕竟是一个对象。这意味着,如果您正在调用的方法someMethod返回一个非对象(包括void),则最终可能会导致垃圾指针值被保留/释放并崩溃。

附加参数

一个考虑因素是,这将发生相同的警告,performSelector:withObject:并且您可能在未声明该方法如何使用参数的情况下遇到类似的问题。ARC允许声明消耗的参数,如果该方法使用了该参数,您最终可能会向僵尸发送消息并崩溃。有多种方法可以解决桥接转换问题,但实际上最好只使用IMP上面and函数指针方法。由于消耗的参数很少出现问题,因此不太可能出现。

静态选择器

有趣的是,编译器不会抱怨静态声明的选择器:

[_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实际上,如果将对象声明为asid并且未导入所有标头,则可能会得到错误的信息您可能最终会崩溃,导致编译器认为正常的代码崩溃。这是非常罕见的,但有可能发生。通常,您会收到一条警告,它不知道从两种方法签名中选择哪一种。

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

performSelector可能会导致泄漏,因为其选择器未知IN Singleton Class / FUNCTION Pointer -Passing Function as parameter

防止“ PerformSelect的选择器未知,可能会导致泄漏”的警告

iOS-可能会删除nsmutablearray导致内存泄漏?

避免使用$ rootScope。$ on-可能会导致内存泄漏

iOS可能会泄漏对象属性

什么是服务器端问题,可能会导致MethodNotAllowedHttpException

未知选择器问题

SQL可能会导致循环或多个级联路径

FK约束可能会导致循环或多个级联路径

变化的行为可能会导致精度损失

statsd 的副作用可能会导致额外的延迟

读取rand.Reader时可能会导致错误?

多次从磁盘读取可能会导致瓶颈

Android:使用getResources可能会导致我的应用崩溃

事务备份可能会导致操作死锁等吗?

选择器OpenDevice的未知类方法

选择器'indexPathForCell:'的未知类方法

为什么斜杠字符会导致我的选择器失败?

设置NSUnderlineStyle会导致无法识别的选择器异常

单击保存按钮会导致“无法识别的选择器发送到实例”

物料日期选择器总是会导致时刻错误

为什么添加选择器标签会导致Web应用程序冻结?

更改Material-UI中的原色会导致Material-UI日期选择器崩溃

Swift:使用对象工厂会导致无法识别的选择器

SASS复合选择器导致错误

MySQL触发器可能会自动失败吗?

内部类具有对外部类的隐式引用,并且可能会泄漏内存

检查内存泄漏时,Valgrind可能会丢失消息-C

SQL可能会泄漏连接,即使我正在调用Connection.Close()