performSelector may cause a leak because its selector is unknown

转自: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 返回值

 

转载于:https://www.cnblogs.com/ygm900/p/7843655.html

<think>好的,我现在需要帮助用户解决关于iOS开发中废弃方法的问题。用户提到的是[object performSelector:@selector(openURL:) withObject:destinationURL]; 这个方法被废弃了,想知道替换方法是什么。 首先,我得确认这个方法的上下文。performSelector:withObject:通常用于动态调用方法,而这里的具体方法是openURL:,这应该是用于打开URL的功能。在iOS中,打开URL通常涉及到UIApplication的openURL方法。 接下来,我需要回忆一下iOS版本的变化。记得从iOS 10开始,苹果引入了一些新的API,并且废弃了一些旧的方法。特别是UIApplication的openURL被替换为openURL:options:completionHandler:方法。不过,用户这里使用的是performSelector来调用openURL:,所以可能需要直接替换为新的方法调用方式。 另外,还需要考虑线程问题。原来的performSelector如果在主线程之外调用可能会有问题,而新的方法可能要求在主线程执行。此外,用户可能还需要处理选项参数和完成回调,不过如果不需要这些,可以传递nil或者空的字典。 然后,我需要检查是否有相关的官方文档或开发者资源支持这一点。例如,苹果的UIApplication文档中确实提到openURL:在iOS 10后被废弃,推荐使用带有options和completion handler的新方法。因此,正确的替换应该是使用UIApplication的shared实例调用openURL:options:completionHandler:方法,传递适当的参数。 用户原来的代码是通过performSelector动态调用openURL:,这可能是因为他们想动态地调用方法,或者有其他特定需求。但一般来说,直接调用新方法是更好的选择。因此,建议用户将代码改为使用新方法,并处理可能的选项和完成回调。 最后,我需要将这个过程整理成一个清晰的步骤,说明废弃原因、推荐替换方法以及示例代码,同时提醒线程安全和参数处理的问题。确保用户能够理解如何迁移他们的代码到新的API,并避免常见的错误。</think>在 iOS 开发中,`performSelector:withObject:` 结合 `openURL:` 的方式已被废弃,以下是替代方案的具体说明: --- ### 废弃原因 旧方法 `[object openURL:url]` 和通过 `performSelector` 的间接调用方式存在以下问题: - 缺少异步回调机制 - 无法指定打开 URL 的附加选项 - 存在线程安全隐患 - 不符合现代 API 设计规范 --- ### 推荐替代方案(iOS 10+) 使用 `UIApplication` 的现代 API: ```objective-c [[UIApplication sharedApplication] openURL:destinationURL options:@{} completionHandler:^(BOOL success) { // 可选:处理打开结果 NSLog(@"URL打开%@", success ? @"成功" : @"失败"); }]; ``` --- ### 关键改进说明 1. **线程安全**:新方法自动在主线程执行 2. **扩展选项**(常用选项示例): ```objective-c @{ UIApplicationOpenURLOptionUniversalLinksOnly : @YES, // 仅通过通用链接打开 UIApplicationOpenURLOptionAnnotationKey : @"customData" // 携带附加数据 } ``` 3. **异步回调**:可准确获知打开操作结果 4. **兼容性**:需 iOS 10+,如需支持旧系统需做版本判断 --- ### 迁移注意事项 - 若仍需使用 `performSelector` 机制(不推荐),应改为: ```objective-c [object performSelector:@selector(openURL:options:completionHandler:) withObject:destinationURL withObject:@{}]; // 注意:此方式无法正确传递 completionHandler 块 ``` - 对于 Swift 项目应使用: ```swift UIApplication.shared.open(destinationURL, options: [:]) { success in // 处理回调 } ``` 建议直接使用标准调用方式以确保代码可维护性和功能完整性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值