PerformSelector 可能会导致泄漏,因为它的选择器未知

2023-11-24

我收到 ARC 编译器发出的以下警告:

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

这就是我正在做的:

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

为什么我会收到此警告?我知道编译器无法检查选择器是否存在,但是为什么这会导致泄漏呢?我怎样才能改变我的代码,这样我就不会再收到这个警告了?


Solution

编译器对此发出警告是有原因的。简单地忽略此警告的情况很少见,而且很容易解决。就是这样:

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);

解释

What's going on here is you're asking the controller for the C function pointer for the method corresponding to the controller. All NSObjects respond to methodForSelector:, but you can also use class_getMethodImplementation in the Objective-C runtime (useful if you only have a protocol reference, like id<SomeProto>). These function pointers are called IMPs, and are simple typedefed function pointers (id (*IMP)(id, SEL, ...))1. This may be close to the actual method signature of the method, but will not always match exactly.

一旦你拥有了IMP,您需要将其转换为函数指针,其中包含 ARC 所需的所有详细信息(包括两个隐式隐藏参数self and _cmd每个 Objective-C 方法调用)。这是在第三行处理的((void *)右侧只是告诉编译器您知道自己在做什么,并且不会生成警告,因为指针类型不匹配)。

Finally, you call the function pointer2.

复杂的例子

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

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;

警告的理由

The reason for this warning is that with ARC, the runtime needs to know what to do with the result of the method you're calling. The result could be anything: void, int, char, NSString *, id, etc. ARC normally gets this information from the header of the object type you're working with.3

There are really only 4 things that ARC would consider for the return value:4

  1. 忽略非对象类型(void, int, etc)
  2. 保留对象值,然后在不再使用时释放(标准假设)
  3. 当不再使用时释放新的对象值(方法在init/ copy家庭或归因于ns_returns_retained)
  4. 什么都不做并假设返回的对象值在本地范围内有效(直到最内部的释放池被耗尽,归因于ns_returns_autoreleased)

致电给methodForSelector:假设它调用的方法的返回值是一个对象,但不保留/释放它。因此,如果您的对象应该像上面#3 中那样被释放(即您调用的方法返回一个新对象),那么您最终可能会造成泄漏。

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

附加参数

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

静态选择器

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

[_controller performSelector:@selector(someMethod)];

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

抑制

在试图考虑有必要抑制此警告和良好的代码设计的情况时,我一片空白。如果有人有过需要消除此警告的经验(并且上述方法不能正确处理问题),请分享。

More

可以建立一个NSMethodInvocation也可以处理这个问题,但是这样做需要更多的输入并且速度也较慢,所以没有理由这样做。

History

当。。。的时候performSelector:方法族首先添加到 Objective-C 中,ARC 并不存在。在创建 ARC 时,Apple 决定应该为这些方法生成警告,以指导开发人员使用其他方法来明确定义通过命名选择器发送任意消息时应如何处理内存。在 Objective-C 中,开发人员可以通过对原始函数指针使用 C 风格转换来实现此目的。

随着 Swift 的推出,Apple已记录 the performSelector:一系列方法被视为“本质上不安全”,并且它们不适用于 Swift。

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

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

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


1 All Objective-C methods have two hidden arguments, self and _cmd that are implicitly added when you call a method.

2 Calling a NULL function is not safe in C. The guard used to check for the presence of the controller ensures that we have an object. We therefore know we'll get an IMP from methodForSelector: (though it may be _objc_msgForward, entry into the message forwarding system). Basically, with the guard in place, we know we have a function to call.

3 Actually, it's possible for it to get the wrong info if declare you objects as id and you're not importing all headers. You could end up with crashes in code that the compiler thinks is fine. This is very rare, but could happen. Usually you'll just get a warning that it doesn't know which of two method signatures to choose from.

4 See the ARC reference on retained return values and unretained return values for more details.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PerformSelector 可能会导致泄漏,因为它的选择器未知 的相关文章

随机推荐

  • d3 - 查看特定 x,y 位置有什么

    我正在尝试在 d3 树中实现一些拖放功能 其中当拖动节点时 如果它直接到节点左侧 50 像素 我想绘制一个虚线连接器来指示如果释放该节点应该是小时候搬到这里 为了做到这一点 我的想法是检查左侧 50 像素的元素 有没有办法查看 d3 中特定
  • 为什么无法通过新的 Azure 门户配置 Azure 诊断以使用 Azure 表存储?

    我正在开发一个将托管在 Azure 中的 Web api 我想使用 Azure 诊断将错误记录到 Azure 表存储中 在经典门户中 我可以将日志配置为转到 Azure 表存储 经典门户诊断设置 然而 在新的 Azure 门户中 我唯一的存
  • 在Windows Azure中使用Redis实现进程外缓存

    我一直在开发一个网页 该网页显示我在天蓝色云中的数据库中的表格 为了减少直接调用数据库以提高性能我想为页面构建一个缓存 目前 我拥有一个内存缓存 进程内 reads表的 现在我想创建一个进程外缓存 应该从什么时候更新writes进行 意味着
  • 使用 caret 包应用 k 折交叉验证模型

    首先我要说的是 我读过很多关于交叉验证的帖子 但似乎存在很多混乱 我的理解很简单 执行 k 倍交叉验证 即 10 倍 以了解 10 倍的平均误差 如果可以接受 则在完整的数据集上训练模型 我正在尝试使用构建决策树rpart在 R 中并利用c
  • JQuery:元素处于视图中时触发操作

    在我网站的页脚中 我使用 counUp js 链接 http inorganik github io countUp js 来计算三个数字 我在网站底部添加了这段代码
  • Primefaces 使用 CSS 样式化组件类

    如何使用 CSS 更改组件的属性 假设我有两个按钮
  • 取同一天 pandas 内数据的平均值

    我有一个数据框df包含测量日期和测量值 duration km df Out 20 Date duration km 0 2015 03 28 09 07 00 800001 0 0 1 2015 03 28 09 36 01 819998
  • OLEDB 连接字符串中的 IMEX 是什么?

    Provider Microsoft Jet OLEDB 4 0 Data Source localhost Extended Properties Excel 8 0 HDR Yes IMEX 2 目的是什么IMEX 2在上面的连接字符串
  • 使 SQL Server 索引较小

    我们在一个项目中使用 SQL Server 2005 系统的用户能够使用 关键字 搜索某些对象 我们实现这一点的方法是为每个表中可能包含这些 关键字 的重要列创建一个全文目录 然后使用 CONTAINS 搜索用户在该索引的搜索框中输入的关键
  • 对 super() 的调用必须是构造函数主体中的第一条语句

    我正在编写一个 LoginRequest 类的构造函数 它扩展了一个名为 JsobObjectRequest 的类 来自 Android 中的 Volley 框架 但这与问题完全无关 使用此代码 public LoginRequest St
  • 删除附有foreign/Hmisc SPSS导入功能的变量标签

    像往常一样 我得到了一些 SPSS 文件 并将其导入到 R 中spss get函数来自Hmisc包裹 我很烦恼labelled类那个Hmisc spss get添加到所有变量data frame 因此想要将其删除 labelled当我尝试跑
  • 如何在Rmarkdown中添加目录?

    我正在使用 RStudio 编写 Markdown 文档 并希望在文档顶部添加目录 TOC 以便用户可以单击相关部分进行阅读 rpubs 上有一些相关的例子 但现在我似乎找不到它们 请注意 我不使用pandoc我很新Rmd knitr 有没
  • 如何以编程方式检测 Office 2019?

    我们习惯于通过获取完整版本 例如从已安装的 Office 可执行文件的文件信息 来检测当前安装的 Office 版本 并将主要版本映射到友好名称 例如 15 映射到 Office 2013 16 映射到 Office 2016 不过 Off
  • 使用 Jena 获得对类的 OWL 限制

    使用披萨本体论 我希望能够查找所有的配料American比萨 如果我在 Prot g 中打开本体 我可以看到American披萨有以下限制 hasTopping some MozerellaTopping hasTopping some T
  • R 使用 readHTMLTable 时出错

    我正在使用以下代码 url http finance yahoo com q op s DIA m 2013 07 library XML tabs readHTMLTable url stringsAsFactors F 我收到以下错误
  • 是否可以在 Mac OS 上调试 x64 程序集?

    我希望能够在我的 Mac 上使用 Sierra 10 12 4 编写和调试 x64 程序集 人们可能会认为这不会是一个特别困难或晦涩的愿望 但尽管花费了很多时间的努力和大量的在线搜索 我仍然没有成功 而且我还没有找到其他人有这样的愿望 我更
  • android中如何给每个用户免费试用期

    我想发布一个 Android 应用程序 试用期为 15 天 之后将收取终身订阅费 我看到 Play 商店开发者帐户中有一个试用期选项 所以我的问题是 如果用户在试用期内取消订阅 之后用户将能够使用该应用程序吗 我不想在我的 apk 文件中更
  • 在第一个索引处插入新元素到集合时,如何使用 PageTabViewStyle 修饰符更新 TabView 中的 SwiftUI 的 ForEach

    这是使用时的一个非常具体的问题ForEach in a TabView with PageTabViewStyle修饰符 每次我在数组的开头插入一个元素时 我的应用程序都会崩溃 我得到了一个 尝试从更新前仅包含 11 项的第 0 部分中删除
  • 用作睡眠的空 for 循环会被优化掉吗? [复制]

    这个问题在这里已经有答案了 我正在查看一些要审查的代码 并遇到了这样的繁忙等待 int loop us 32 int x for x 0 x
  • PerformSelector 可能会导致泄漏,因为它的选择器未知

    我收到 ARC 编译器发出的以下警告 performSelector may cause a leak because its selector is unknown 这就是我正在做的 controller performSelector