performSelector may cause a leak because its selector is unknown

本文介绍了在Objective-C的自动引用计数(ARC)环境下,如何避免使用performSelector时出现可能导致内存泄漏警告的问题。提供了两种解决方案:一是通过IMP获取函数指针并直接调用,二是使用宏定义来忽略警告。

在Objective-C中需要以函数名或者函数指针来调用函数时,以回调函数为例,对象为(id)target,它的成员函数名为callback,之前习惯是这么写的:

if ([target respondsToSelector:callback]){
    [target performSelector:callback withObject:nil];
}

但是在ARC下会报一个warning: PerformSelector may cause a leak because its selector is unknown

在网上查,很多人说的方法都是定义宏去ignore warning之类的。总感觉很不爽。今天查到了正确的解决方法:

【解决方法】

1. 比较好

if ([target respondsToSelector:callback]){
//    [target performSelector:callback withObject:nil];
    IMP imp = [target methodForSelector:callback];
    void (*func)(id, SEL) = (void *)imp;
    func(target, callback);
}

这样就不会报warning了。


2.  使用宏定义,忽略警告,也可以达到不报警的目的

在非ARC项目中没有这个警告。如果是在某一处修改只需要加入下列代码:

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

如果有返回值,可以按如下方式调用:

<span style="font-size:14px;">id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);</span>

在 iOS 开发中,当两个模块(如 `ARITheme` 和 `ARIKit`)之间出现循环依赖时,意味着它们彼此直接或间接地引用了对方,导致编译失败或运行时行为异常。这种问题在大型项目或模块化架构中较为常见,尤其在使用静态库或静态框架时更容易暴露出来。 解决此类问题的核心思路是打破依赖链条,使模块之间的引用变为单向依赖或通过中间层解耦。以下是几种常见的解决方案: ### 使用协议(Protocol)进行解耦 可以定义一个独立的协议头文件,将 `ARITheme` 和 `ARIKit` 之间互相依赖的接口抽象为协议,由具体模块实现这些协议。这样可以避免头文件的直接引入,从而消除循环依赖。 ```objc // ARIKitProtocol.h @protocol ARIKitProtocol <NSObject> - (void)doSomething; @end // ARIThemeProtocol.h @protocol ARIThemeProtocol <NSObject> - (void)applyTheme; @end ``` 在具体实现中,模块分别引用对应的协议头文件,而非具体的类头文件。这种设计方式提高了模块之间的独立性,并有助于后续扩展和测试[^1]。 ### 使用依赖注入(Dependency Injection) 通过构造函数或属性将依赖对象传递进去,而不是在模块内部直接创建或引用对方的实例。这种方式可以延迟依赖的解析,避免编译阶段的循环引用问题。 ```objc // ARIKit.h #import "ARIThemeProtocol.h" @interface ARIKit : NSObject - (instancetype)initWithTheme:(id<ARIThemeProtocol>)theme; @end // ARITheme.m #import "ARIKitProtocol.h" @implementation ARITheme - (void)applyTheme { id<ARIKitProtocol> kit = ...; [kit doSomething]; } @end ``` 这种方式要求在初始化时完成依赖的绑定,适用于模块化程度较高、依赖关系明确的项目[^1]。 ### 提取公共抽象层 将 `ARITheme` 和 `ARIKit` 中相互依赖的部分提取到一个新的模块中,如 `ARICore`。该模块不包含具体实现,仅定义接口和数据结构,供其他模块引用。这种方案适用于多个模块之间存在复杂依赖关系的情况,有助于降低耦合度并提高可维护性。 ### 使用运行时(Runtime)动态调用 在某些场景下,可以通过 `NSObject` 的 `performSelector:` 方法或 `NSInvocation` 在运行时动态调用对方的方法,避免在编译阶段出现依赖冲突。这种方式虽然可以绕过编译问题,但牺牲了编译时的安全性检查,建议仅在必要时使用。 ```objc if ([target respondsToSelector:@selector(doSomething)]) { [target performSelector:@selector(doSomething)]; } ``` 该方法适用于插件化架构或热更新机制中,对模块间动态交互有较高灵活性要求的场景。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值