前言
我们平时开发中,如果使用的是ARC模式,那么一旦使用performSelector
去调用函数,就一定会出现下面的内存警告现象。
你可能会疑惑,只不过是用了 “performSelector:” 去调用方法,为什么会提示可能会引起内存泄漏问题呢?然后开发节奏紧张的情况下,大部分人去方法上网搜一下解决方法都在看到如何解决这个警告,然后把代码复制黏贴一下,发现可行后就把问题放下了,那么出现这个警告的原因到底是因为什么?这篇文章现在就来解答一下这个问题。点个收藏,日后有空看看原理时再来看也不迟~
一、告警的原因解答
在ARC下使用performSelector:
函数会出现这个警告的主要原因是因为:编译器无法确定在运行时执行的方法是否需要内存管理。 在编译时,编译器并不知道performSelector:
将要执行哪个方法,所以无法在编译时为该方法生成正确的内存管理代码,从而可能会有内存泄漏的风险。
我们都知道,在ARC环境下,编译器会自动帮我们管理内存,当对象的引用计数为0时,编译器会自动释放这个对象,无需手动调用release
方法。然而,performSelector:
函数是在运行时根据传入的参数来动态调用方法的。在编译时,编译器并不知道将要调用的方法是什么,也不了解其方法签名及返回值,甚至连是否有返回值都不清楚。而且,由于编译器不知道方法名,所以就没办法运用 ARC 的内存管理规则来判定返回值是不是应该释放。因此,ARC 采用了比较谨慎的做法,就是不添加释放操作。然而这么做可能导致内存泄漏,因为方法在返回对象时可能已经将其保留了。
举个例子,假如我们定义了一个Person
类,然后创建方法和释放方法中加入相关日志:
@interface Person : NSObject@end@implementation Person- (instancetype)init{self = [super init];if (self) {NSLog(@"我创建了");}return self;}-(void)dealloc{NSLog(@"我释放了");}@end@interface Person : NSObject @end @implementation Person - (instancetype)init { self = [super init]; if (self) { NSLog(@"我创建了"); } return self; } -(void)dealloc { NSLog(@"我释放了"); } @end@interface Person : NSObject @end @implementation Person - (instancetype)init { self = [super init]; if (self) { NSLog(@"我创建了"); } return self; } -(void)dealloc { NSLog(@"我释放了"); } @end
现在我们先用objc代码正常new一个Person
类
[Person new];[Person new];[Person new];
运行结果如下
然后我们把生成的函数改成使用performSelector:
函数来调用new
方法:
[Person performSelector:@selector(new)];[Person performSelector:@selector(new)];[Person performSelector:@selector(new)];
运行结果如下
从在上面的代码中,可以得出使用performSelector:
函数生成的实例并没有打印释放日志,尽管new
函数也生成了一个Person
类实例,但是在编译器在编译时期并不知道是什么函数,有没有返回值,所以统一让ARC采用不释放的保守操作,因此ARC并没有去释放这个实例,从而导致了内存泄漏。
二、告警的解决办法
其实在网上搜一下,就能找到很多解决的版本,第一个就是用编译指令去掉警告,其函数如下
#pragma clang diagnostic push#pragma clang diagnostic ignored "-Warc-performSelector-leaks"//performSelector函数#pragma clang diagnostic pop#pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" //performSelector函数 #pragma clang diagnostic pop#pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" //performSelector函数 #pragma clang diagnostic pop
这段代码的作用是忽略使用performSelector:
函数可能导致内存泄漏的警告。但是如果要使用这个解决办法,我们需要保证自己的代码中不存在内存泄漏的风险,这是有风险的,因此并不建议使用。
第二个方法就是去获取performSelector:
函数想调用的那个方法对应的对应的 C 函数指针。此处对应例子里的是Person
类对象的new
方法的函数指针,然后使用该函数指针来调用new
方法创建了一个Person
类的实例。但是需要注意的是,这种方法也只是消除了警告,实际如果还是调用new
,系统并不会帮我们释放该实例,需要我们使用CFBridgingRelease
函数将实例转换成Core Foundation对象,并手动释放其内存。写法如下
Class personClass = [Person class];SEL selector = @selector(new);IMP imp = [personClass methodForSelector:selector];Person *(*func)(id, SEL) = (void *)imp;Person *person = func(personClass, selector);// 使用person对象// ...// 释放person对象CFBridgingRelease((__bridge CFTypeRef)(person));Class personClass = [Person class]; SEL selector = @selector(new); IMP imp = [personClass methodForSelector:selector]; Person *(*func)(id, SEL) = (void *)imp; Person *person = func(personClass, selector); // 使用person对象 // ... // 释放person对象 CFBridgingRelease((__bridge CFTypeRef)(person));Class personClass = [Person class]; SEL selector = @selector(new); IMP imp = [personClass methodForSelector:selector]; Person *(*func)(id, SEL) = (void *)imp; Person *person = func(personClass, selector); // 使用person对象 // ... // 释放person对象 CFBridgingRelease((__bridge CFTypeRef)(person));
运行结果如下
三、总结
本文根据我的理解和实践总结出了ARC中performSelector:
的内存泄漏问题的原因和解决办法,由于Objc是一门非常动态的语言,开发者可以随意调用任何方法,performSelector:
就是其中之一,但是这个警告出现的原因还是需要注意的,虽说有些代码还是会经常用到它们,但是如果不想出现问题建议还是尽量少用。