iOS|ARC中performSelector的内存泄漏问题

前言

我们平时开发中,如果使用的是ARC模式,那么一旦使用performSelector去调用函数,就一定会出现下面的内存警告现象。

image.png
你可能会疑惑,只不过是用了 “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];

运行结果如下

image.png

然后我们把生成的函数改成使用performSelector:函数来调用new方法:

[Person performSelector:@selector(new)];
[Person performSelector:@selector(new)]; 
[Person performSelector:@selector(new)];

运行结果如下

image.png

从在上面的代码中,可以得出使用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));

运行结果如下

image.png

三、总结

本文根据我的理解和实践总结出了ARC中performSelector:的内存泄漏问题的原因和解决办法,由于Objc是一门非常动态的语言,开发者可以随意调用任何方法,performSelector:就是其中之一,但是这个警告出现的原因还是需要注意的,虽说有些代码还是会经常用到它们,但是如果不想出现问题建议还是尽量少用。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY2y3Tvh' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片