转自:http://www.jianshu.com/p/6517ab655be7

问题

我在 ARC 模式下编译出了这个 warning:

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

我的代码是这么写的:

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

为什么会有这个 warning 呢?我知道编译器无法检查实际上有没有这个 selector,不过这为什么会造成内存泄漏呢?代码应该怎么改才能消除这个 warning?


答案

答案1:单纯消除 warning

Scott Thompson,1100 票

LLVM 3.0 编译器可以用以下代码消除 warning:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

如果在多个地方都要用,可以定义一个宏:

#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)

用的时候:

SuppressPerformSelectorLeakWarning(
[_target performSelector:_action withObject:self]
);

如果需要返回值:

id result;
SuppressPerformSelectorLeakWarning(
result = [_target performSelector:_action withObject:self]
);

答案2:详细解释和正统解决

wbyoung,768 赞

解决方案

编译器报这个 warning 是有原因的,一般不应该直接忽略,而且消除这个 warning 并不难。如下即可:

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

代码解释

这一堆代码在做的事情其实是,向 controller 请求那个方法对应的 C 函数指针。所有的NSObject都能响应methodForSelector:这个方法,不过也可以用 Objective-C runtime 里的class_getMethodImplementation(只在 protocol 的情况下有用,id<SomeProto>这样的)。这种函数指针叫做IMP,就是typedef过的函数指针(id (*IMP)(id, SEL, ...)[1])。它跟方法签名(signature)比较像,虽然可能不是完全一样。

得到IMP之后,还需要进行转换,转换后的函数指针包含 ARC 所需的那些细节(比如每个 OC 方法调用都有的两个隐藏参数self_cmd)。这就是代码第 4 行干的事(右边的那个(void *)只是告诉编译器,不用报类型强转的 warning)。

最后一步,调用函数指针[2]

更复杂的例子

如果 selector 接收参数,或者有返回值,代码就需要改改:

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;

为什么会有这个 warning

原因是这样的:我们在 ARC 下调一个方法,runtime 需要知道对于返回值该怎么办。返回值可能有各种类型:voidintcharNSString *id等等。ARC 一般是根据返回值的头文件来决定该怎么办的[3],一共有以下 4 种情况[4]

  1. 直接忽略(如果是基本类型比如 voidint这样的)。
  2. 把返回值先 retain,等到用不到的时候再 release(最常见的情况)。
  3. 不 retain,等到用不到的时候直接 release(用于 initcopy 这一类的方法,或者标注ns_returns_retained的方法)。
  4. 什么也不做,默认返回值在返回前后是始终有效的(一直到最近的 release pool 结束为止,用于标注ns_returns_autoreleased的方法)。

而调performSelector:的时候,系统会默认返回值并不是基本类型,但也不会 retain、release,也就是默认采取第 4 种做法。所以如果那个方法本来应该属于前 3 种情况,都有可能会造成内存泄漏。

对于返回void或者基本类型的方法,就目前而言你可以忽略这个 warning,但这样做不一定安全。我看过 Clang 在处理返回值这块儿的几次迭代演进。一旦开着 ARC,编译器会觉得从performSelector:返回的对象没理由不能 retain,不能 release。在编译器眼里,它就是个对象。所以,如果返回值是基本类型或者void,编译器还是存在会 retain、release 它的可能,然后直接导致 crash。

带参数调用

类似地,performSelector:withObject:也会报同一个 warning,因为不指明怎么处理参数也会有同样的问题。ARC 允许为方法参数标注consumed,如果你调的方法有这种标注,最终可能导致把消息发给僵尸对象然后 crash。要解决这个问题可以用桥接(bridged casting),但是最好最简单的方法还是我上面写的用IMP和函数指针的方法。不过给参数标 consumed 是比较少见的,所以这个问题也不容易发生。

静态 selector

有趣的是,下面这种静态声明的 selector 就不会出 warning:

[_controller performSelector:@selector(someMethod)];

原因是,这种情况下编译器就能在编译阶段得到关于这个 selector 的全部信息,不需要默认任何事情。


[1]: 所有的 Objective-C 方法都有两个隐藏的参数,self_cmd,调用时自动加的。

[2]: 在 C 里调用NULL方法是不安全的。而if (!_controller) { return; }这一句保证controller不为空,所以我们一定能从methodForSelector:得到一个IMP(虽然可能只是_objc_msgForward,进入消息转发系统)。基本上,有了这行检查,就能保证我们有方法可调。

[3]: 实际上,如果返回值的类型是id,而你又没 import 对应的头文件,它是有可能做出错误处理的。有可能会 crash 在一块编译器以为安全的代码里。这种情况很罕见,但还是有发生的可能。一般来说,如果编译器不知道该选哪个方法签名,它会报一个 warning 的。

[4]: 更多细节请参考 ARC 的文档 retain 返回值不 retain 返回值

performSelector may cause a leak because its selector is unknown的更多相关文章

  1. ios之"performSelector may cause a leak because its selector is unknown"警告原因及其解决办法

    问题描述 项目中使用到了从字符串创建选择器,编译时发现警告:"performSelector may cause a leak because its selector is unknown ...

  2. PerformSelector may cause a leak because its selector is unknown 解决方法

    我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3801030.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的阅读体验 ...

  3. performSelector may cause a leak because its selector is unknown解决

    解决方法 SEL selector = NSSelectorFromString(@"applySketchFilter:"); IMP imp = [FWApplyFilter ...

  4. objective-c "performSelector may cause a leak because its selector is unknown".

    #define SuppressPerformSelectorLeakWarning(Stuff) \ do { \ _Pragma("clang diagnostic push" ...

  5. warning:performSelector may cause a leak because its selector

    warning:performSelector may cause a leak because its selector     在ARC项目中使用 performSelector: withObj ...

  6. performSelector的原理以及用法

    一.performSelector调用和直接调用区别下面两段代码都在主线程中运行,我们在看别人代码时会发现有时会直接调用,有时会利用performSelector调用,今天看到有人在问这个问题,我便做 ...

  7. Objective-C中一种消息处理方法performSelector: withObject:

    Objective-C中调用函数的方法是“消息传递”,这个和普通的函数调用的区别是,你可以随时对一个对象传递任何消息,而不需要在编译的时候声明这些方法.所以Objective-C可以在runtime的 ...

  8. 【转】Objective-C中一种消息处理方法performSelector: withObject:

    原文 : http://www.cnblogs.com/buro79xxd/archive/2012/04/10/2440074.html   Objective-C中调用函数的方法是“消息传递”,这 ...

  9. iOS警告收录及科学快速的消除方法

    来自: http://www.cnblogs.com/dsxniubility/p/4757760.html iOS警告收录及科学快速的消除方法     前言:现在你维护的项目有多少警告?看着几百条警 ...

随机推荐

  1. 分布式系统的CAP理论

    一.CAP理论概述 一个分布式系统最多只能同时满足一致性(Consistency).可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项. 二.CAP ...

  2. jenkins(2): jenkins定时构建项目

    参考:http://blog.sina.com.cn/s/blog_b5fe6b270102v7xo.html https://blog.csdn.net/xueyingqi/article/deta ...

  3. UOJ#351. 新年的叶子 概率期望

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ351.html 题目传送门 - UOJ351 题意 有一个 n 个节点的树,每次涂黑一个叶子节点(度为 1 ...

  4. Codeforces Gym100187C Very Spacious Office 贪心 堆

    原文链接https://www.cnblogs.com/zhouzhendong/p/CF-Gym100187C.html 题目传送门 - CF-Gym100187C 题意 给定 $n$ 个房间以及 ...

  5. boost 正则表达式 regex

    boost 正则表达式 regex   环境安装 如果在引用boost regex出现连接错误,但是引用其他的库却没有这个错误,这是因为对于boost来说,是免编译的,但是,正则这个库 是需要单独编译 ...

  6. Gym 102091K The Stream of Corning 2【线段树】

    <题目链接> 题目大意: 进行两种操作:1.给定一个数的出现时间.价值.消失时间: 2.进行一次询问,问你当前时间,第K大的数的价值. 解题分析: 采用离线集中处理,将每个数的出现时间和它 ...

  7. 从URL输入到页面展现到底发生什么

    前言 打开浏览器从输入网址到网页呈现在大家面前,背后到底发生了什么?经历怎么样的一个过程?先给大家来张总体流程图,具体步骤请看下文分解!   从URL输入到页面展现 总体来说分为以下几个过程: DNS ...

  8. 006.Ceph对象存储基础使用

    一 Ceph文件系统 1.1 概述 Ceph 对象网关是一个构建在 librados 之上的对象存储接口,它为应用程序访问Ceph 存储集群提供了一个 RESTful 风格的网关 . Ceph 对象存 ...

  9. 关于MVC框架中的Model的理解

    一直以来对MVC结构中的Model层很不理解. 虽然Model层封装了一些方法,在查询和属性访问的时候提供了一些便利,但是实际使用中更多感受到的是Model层带来的限制,用起来十分别扭. 之前继承的一 ...

  10. Lua的 table.sort排序

    在用table.sort 排序的时候注意,如果使用多个条件排序,应在一个排序函数里按照条件优先级进行比较排序. 例如 local t = { {time = , i = }, {time = , i ...