ARC编译器向我发出以下警告:

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

以下是我正在做的:

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

为什么我会收到这个警告?我知道编译器不能判断 Select 器是否存在,但是为什么会导致泄漏呢?我怎样才能更改我的代码,使我不再收到此警告呢?

推荐答案

解决方案

编译器对此发出警告是有原因的.这种警告很少被忽略,而且很容易解决.以下是方法:

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>,则非常有用).这些函数指针称为IMPs,是简单的typedefed函数指针(id (*IMP)(id, SEL, ...))1.这可能接近方法的实际方法签名,但并不总是完全匹配.

获得IMP之后,需要将其转换为函数指针,该指针包含ARC需要的所有细节(包括每个Objective-C方法调用的两个隐式隐藏参数self_cmd).这在第三行中处理(右侧的(void *)简单地告诉编译器您知道自己在做什么,不要生成警告,因为指针类型不匹配).

最后,调用函数指针2.

复杂示例

当 Select 器接受参数或返回值时,您必须进行一些更改:

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中那样释放(即,您调用的方法返回一个新对象),那么您最终可能会造成泄漏.

对于您试图调用的 Select 器(返回值为void或其他非对象),您可以启用编译器功能来忽略该警告,但这可能很危险.我看到Clang在如何处理未赋值给局部变量的返回值方面经历了几次迭代.在启用ARC的情况下,即使您不想使用,它也没有理由不保留和释放从methodForSelector:返回的对象值.从编译器的Angular 来看,它毕竟是一个对象.这意味着如果您正在调用的方法someMethod返回一个非对象(包括void),那么您最终可能会保留/释放一个垃圾指针值并崩溃.

其他参数

需要注意的一点是,performSelector:withObject:也会出现同样的警告,如果不声明该方法如何使用参数,可能会遇到类似的问题.Arc允许声明consumed parameters,如果该方法使用该参数,您最终可能会向僵尸发送一条消息并崩溃.有一些方法可以使用桥式强制转换来解决这个问题,但实际上,简单地使用上面的IMP AND函数指针方法会更好.因为消耗的参数很少是问题,所以这不太可能出现.

静电 Select 器

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

[_controller performSelector:@selector(someMethod)];

这是因为编译器实际上能够在编译期间记录有关 Select 器和对象的所有信息.它不需要对任何事情做出任何假设.(大约一年前,我通过查看来源判断了这一点,但现在没有参考资料.)

suppress

当我试图想出这样一种情况,即有必要取消此警告并进行良好的代码设计时,我的结果是一片空白.如果有人有过需要将此警告静音的经验,请与其分享(上面的警告没有正确处理问题).

更多

也可以建立一个NSMethodInvocation来处理这个问题,但这样做需要更多的打字,而且速度也较慢,所以没有什么理由这么做.

历史

performSelector:系列方法第一次被添加到Objective-C时,ARC还不存在.在创建ARC时,苹果决定应该为这些方法生成警告,以 bootstrap 开发人员使用其他方法明确定义通过命名 Select 器发送任意消息时应如何处理内存.在Objective-C中,开发人员可以通过在原始函数指针上使用C样式强制转换来做到这一点.

随着Swift的推出,Apple has documentedperformSelector:系列方法视为"天生不安全",Swift无法使用这些方法.

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

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

然而,基于命名 Select 器发送消息的 idea 并不是一个"本质上不安全"的功能.这一思想已经在Objective-C以及许多其他编程语言中成功地使用了很长一段时间.


1所有Objective-C方法都有两个隐藏参数,self_cmd,在调用方法时隐式添加.

2在C中调用NULL函数是不安全的.用于判断控制器是否存在的警卫确保我们有一个对象.因此,我们知道我们将从methodForSelector:中获得IMP(尽管它可能是_objc_msgForward,进入消息转发系统).基本上,有了守卫,我们知道我们有一个函数可以调用.

3实际上,如果将您的对象声明为id并且您没有导入所有标头,则它可能会获得错误的信息.您可能会在编译器认为没有问题的代码中以崩溃告终.这种情况非常罕见,但也有可能发生.通常,您只会收到一条警告,说明它不知道从两个方法签名中 Select 哪一个.

4有关详细信息,请参阅retained return valuesunretained return values上的ARC参考.

Ios相关问答推荐

SwiftUI ScrollView中的默认空间

2024 Beacon应用程序- startMonitoring(适用于:地区)或

iOS中的分段拾取器—手柄点击已 Select 的项目

避免从CoreData加载图像列表时出现SwiftUI挂起

无法在所有窗口上方显示图像

SWIFT AVFoundation不能在查看更新的同时发布更新

如何结合`@dynamicMemberLookup`和`ExpressibleByStringInterpolation`来实现方法链?

不使用故事板创建 iMessage 应用程序

将 URLCache 子类与 URLSession 一起使用

Xcode14 启动时崩溃:dyld:库未加载:/usr/lib/swift/libswiftCoreGraphics.dylib

Flutter IOS 构建错误 - 在签名和功能编辑器中 Select 一个开发团队.

为 ColorPicker 创建一个名为 Color 的计算变量的绑定

迁移到 UIKit 生命周期的应用程序不会调用 SceneDelegate

iOS 11 navigationItem.titleView 宽度未设置

无法将NSTaggedPointerString类型的值转换为NSNumber

如何判断是否为 64 位构建了静态库?

使用 Xcode 的 All Exceptions 断点时忽略某些异常

Objective-C 将 NSDate 设置为当前 UTC

Xcode 5 和 iOS 7:架构和有效架构

UITableView 中的圆形 UIImageView 没有性能影响?