我收到ARC编译器发出的以下警告:
“performSelector可能会导致泄漏,因为其选择器未知”。
以下是我正在做的:
[\u控制器性能选择器:NSSelectorFromString(@“someMethod”);
为什么我会收到这个警告?我知道编译器无法检查选择器是否存在,但为什么会导致泄漏?我怎样才能更改代码,使我不再收到此警告
解决方案
编译器对此发出警告是有原因的。这种警告很少被忽略,而且很容易解决。以下是方法:
如果(!\u控制器){return;}
SEL selector=NSSelectorFromString(@“someMethod”);
IMP IMP=[[控制器方法选择器:选择器];
无效(*func)(id,SEL)=(无效*)imp;
func(_控制器,选择器);
或者更简洁(虽然没有警卫很难阅读):
SEL selector=NSSelectorFromString(@“someMethod”);
((void(*)(id,SEL))[_controllermethodforselector:selector])(_controller,selector);
解释
这里发生的事情是,您要求控制器提供与控制器对应的方法的C函数指针。所有NSObjects都会响应选择程序的methodForSelector:,但您也可以在Objective-C运行时中使用class\u getMethodImplementation(如果您只有协议引用,如id<;SomeProto>;,则非常有用)。这些函数指针称为IMPs,是简单的typedefed函数指针(id(*IMP)(id,SEL,…))1。这可能接近方法的实际方法签名,但并不总是完全匹配
一旦有了IMP,就需要将其转换为一个函数指针,该指针包含ARC需要的所有细节(包括每个Objective-C方法调用的两个隐式隐藏参数self和\u cmd)。这在第三行中处理(右侧的(void*)简单地告诉编译器您知道自己在做什么,并且不生成警告,因为指针类型不匹配)
最后,调用函数指针2
复杂示例
当选择器接受参数或返回值时,您必须稍微更改:
SEL selector=NSSelectorFromString(@“processRegion:of视图:”);
IMP IMP=[[控制器方法选择器:选择器];
CGRect(*func)(id、SEL、CGRect、UIView*)=(void*)imp;
CGRect结果=\u控制器?
func(_controller,selector,someRect,someView):CGRectZero;
警告推理
出现此警告的原因是,对于ARC,运行时需要知道如何处理调用的方法的结果。结果可以是任何内容:void、int、char、NSString*、id,等等。ARC通常从正在处理的对象类型的标头获取此信息。3
对于返回值,只有4个方面会考虑: 4
- 忽略非对象类型(
void,int等) - 保留对象值,然后在不再使用时释放(标准假设)
- 不再使用时释放新对象值(
init/copy系列中的方法或使用ns\u返回\u保留的属性化方法) - 无所事事&;假设返回的对象值在本地范围内有效(直到最内部的发布池被耗尽,并用
ns\u returns\u autoreleased属性化为止)
对methodForSelector:的调用假定它调用的方法的返回值是一个对象,但不保留/释放它。因此,如果您的对象应该像上面的#3中那样被释放(也就是说,您正在调用的方法返回一个新对象),那么您可能最终会创建一个泄漏
对于试图调用returnvoid或其他非对象的选择器,您可以启用编译器功能忽略警告,但这可能很危险。我已经看到Clang在处理未分配给局部变量的返回值方面进行了几次迭代。启用ARC后,即使您不想使用它,也没有理由不能保留和释放从methodForSelector:返回的对象值。从编译器的角度来看,它毕竟是一个对象。这意味着,如果您调用的方法,someMethod,返回一个非对象(包括void),则可能会导致垃圾指针值被保留/释放并崩溃
附加参数
需要考虑的一点是,这与performSelector:withObject:会出现相同的警告,如果不声明该方法如何使用参数,则可能会遇到类似的问题。ARC允许声明已使用的参数,如果该方法使用该参数,您可能最终会向僵尸发送消息并崩溃。有一些方法可以通过桥接转换解决这个问题,但实际上最好只使用上面的IMP和函数指针方法。由于使用的参数很少是一个问题,因此不太可能出现这种情况
静态选择器
有趣的是,编译器不会抱怨静态声明的选择器:
[\u控制器性能选择器:@selector(someMethod)];
这是因为编译器实际上能够在编译期间记录有关选择器和对象的所有信息。它不需要对任何事情做出任何假设。(我在一年前通过查看源代码检查了这一点,但现在没有参考资料。)
压制
在尝试思考一种情况时,抑制这个警告是必要的,并且是良好的代码设计,我的回答是空白的。如果有人有过这样的经历,那么请分享他们是否有必要将此警告静音(而上述情况并不能正确处理)
更多
也可以建立一个NSMethodInvocation来处理这个问题,但是这样做需要更多的输入,而且速度也较慢,所以没有什么理由这么做
历史
当性能选择器:方法系列首次添加到Objective-C时,ARC不存在。在创建ARC时,苹果决定应该为这些方法生成一个警告,以指导开发者使用其他方法明确定义通过命名选择器发送任意消息时应如何处理内存。在Objective-C中,开发人员可以通过在原始函数指针上使用C样式转换来实现这一点
随着Swift的引入,苹果公司将performSelector:方法系列记录为“天生不安全”,Swift无法使用这些方法
随着时间的推移,我们看到了这一进展:
- Objective-C的早期版本允许
性能选择器:(手动内存管理) - 带有ARC警告的Objective-C,用于
性能选择器: - Swift无权访问
性能选择器:,并将这些方法记录为“固有不安全”
然而,基于命名选择器发送消息的想法并不是“固有的不安全”特性。这一思想已经在Objective-C以及许多其他编程语言中成功地使用了很长时间
1所有Objective-C方法都有两个隐藏参数,self和\u cmd,它们在调用方法时隐式添加
2调用NULL函数在C中是不安全的。用于检查控制器是否存在的防护装置确保我们有一个对象。因此,我们知道我们将从方法中获得(尽管它可能是IMP选择器:\u objc\u msgForward,进入消息转发系统)。基本上,有了守卫,我们知道我们有一个函数可以调用
3实际上,如果您将对象声明为id并且没有导入所有标题,它可能会获取错误的信息。编译器认为没有问题的代码可能会崩溃。这是非常罕见的,但可能发生。通常,您只会得到一个警告,它不知道从两个方法签名中选择哪一个
4有关更多详细信息,请参阅ARC参考中的保留返回值和未保留返回值