🔥🔥造成循环引用和内存泄漏的几种情况

Posted 1-434

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了🔥🔥造成循环引用和内存泄漏的几种情况相关的知识,希望对你有一定的参考价值。

ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露。导致ios对象无法按预期释放的一个无形杀手是——循环引用。循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个ViewController,则在dismiss或者pop之后其dealloc无法被调用,在频繁的push或者present之后内存暴增,然后APP就duang地挂了。下面列举我们变成中比较容易碰到的三种循环引用的情形。
 
(1)计时器NSTimer
一方面,NSTimer经常会被作为某个类的成员变量,而NSTimer初始化时要指定self为target,容易造成循环引用。 另一方面,若timer一直处于validate的状态,则其引用计数将始终大于0。先看一段NSTimer使用的例子(ARC模式):
技术图片
在类外部初始化一个Friend对象,并延迟5秒后将friend释放(外部运行在非arc环境下)
技术图片
我们所期待的结果是,初始化5秒后,f对象被release,f的dealloc方法被调用,在dealloc里面timer失效,对象被析构。但结果却是如此:
技术图片
这是为什么呢?主要是因为从timer的角度,timer认为调用方(Friend对象)被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从Friend的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,子子孙孙无穷尽也。问题的症结在于-(void)cleanTimer函数的调用时机不对,显然不能想当然地放在调用者的dealloc中。一个比较好的解决方法是开放这个函数,让Friend的调用者显式地调用来清理现场。如下:
技术图片
(2)block
block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题,一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_otherVar = ...};block的这种循环引用会被编译器捕捉到并及时提醒。举例如下,依旧以Friend类为例子:
技术图片

 

 

我们看到,在block的实现内部又使用了Friend类的arr属性,xcode给出了warning, 运行程序之后也证明了Friend对象无法被析构:
技术图片

 

 


网上大部分帖子都表述为"block里面引用了self导致循环引用",但事实真的是如此吗?我表示怀疑,其实这种说法是不严谨的,不一定要显式地出现"self"字眼才会引起循环引用。我们改一下代码,不通过属性self.arr去访问arr变量,而是通过实例变量_arr去访问,如下:
技术图片

 

 


由此我们知道了,即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!但对于这种情况,我们无法通过加__weak声明或者__block声明去禁止block对self进行强引用或者强制增加引用计数。但我们可以通过其他指针来避免循环引用(多谢xq_120的提醒),具体是这么做的:
技术图片
对于self.arr的情况,我们要分两种环境去解决:
1)ARC环境下:ARC环境下可以通过使用_weak声明一个代替self的新变量代替原先的self,我们可以命名为weakSelf。通过这种方式告诉block,不要在block内部对self进行强制strong引用:(如果要兼容ios4.3,则用__unsafe_unretained代替__weak,不过目前基本不需考虑这么low的版本)
技术图片

 

 


2)MRC环境下:解决方式与上述基本一致,只不过将__weak关键字换成__block即可,这样的意思是告诉block:小子,不要在内部对self进行retain了!
(3)委托delegate
在委托问题上出现循环引用问题已经是老生常谈了,本文也不再细讲,规避该问题的杀手锏也是简单到哭,一字诀:声明delegate时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了!
(4)对象互相强引用
class Father
 
@interface Father: NSObject
@property (strong, nonatomic) Son *son;
 
@end
 
class Son
 
@interface Son: NSObject
@property (strong, nonatomic) Father *father;
 
@end
上述代码有两个类,分别为爸爸和儿子。爸爸对儿子强引用,儿子对爸爸强引用。这样释放儿子必须先释放爸爸,要释放爸爸必须先释放儿子。如此一来,两个对象都无法释放。
解决方法是将Father中的Son对象属性从strong改为weak。
内存泄漏可以用Xcode中的Debug Memory Graph去检查,同时Xcode也会在runtime中自动汇报内存泄漏的问题。
(5)非OC对象内存处理
对于iOS开发,ARC模式已发扬光大多年,可能很多人早已忘记当年retain、release的年代,但ARC的出现并不是说我们完全可以忽视内存泄漏的问题。对于一些非OC对象,使用完毕后其内存仍需要我们手动释放。
举个例子,比如常用的滤镜操作调节图片亮度
CIImage *beginImage = [[CIImage alloc]initWithImage:[UIImage imageNamed:@"yourname.jpg"]];
CIFilter *filter = [CIFilter filterWithName:@"CIColorControls"];
[filter setValue:beginImage forKey:kCIInputImageKey];
[filter setValue:[NSNumber numberWithFloat:.5] forKey:@"inputBrightness"];//亮度-1~1
CIImage *outputImage = [filter outputImage];
//GPU优化
EAGLContext * eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
eaglContext.multiThreaded = YES;
CIContext *context = [CIContext contextWithEAGLContext:eaglContext];
[EAGLContext setCurrentContext:eaglContext];
CGImageRef ref = [context createCGImage:outputImage fromRect:outputImage.extent];
UIImage *endImg = [UIImage imageWithCGImage:ref];
_imageView.image = endImg;
CGImageRelease(ref);//非OC对象需要手动内存释放
在如上代码中的CGImageRef类型变量非OC对象,其需要手动执行释放操作CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free等都需要注意。
(6)地图类处理
若项目中使用地图相关类,一定要检测内存情况,因为地图是比较耗费App内存的,因此在根据文档实现某地图相关功能的同时,我们需要注意内存的正确释放,大体需要注意的有需在使用完毕时将地图、代理等滞空为nil,注意地图中标注(大头针)的复用,并且在使用完毕时清空标注数组等。
- (void)clearMapView{
                self.mapView = nil;
                self.mapView.delegate =nil;
                self.mapView.showsUserLocation = NO;
                [self.mapView removeAnnotations:self.annotations];
                [self.mapView removeOverlays:self.overlays];
                [self.mapView setCompassImage:nil];
}
(7)大次数循环内存暴涨问题
记得有道比较经典的面试题,查看如下代码有何问题:
for(int i = 0; i < 100000; i++) {
            NSString *string = @"Abc";
            string = [string lowercaseString];
            string = [string stringByAppendingString:@"xyz"];
            NSLog(@"%@", string);
}
该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,解决方法为在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
for(int i = 0; i < 100000; i++) {
        @autoreleasepool {
                NSString *string = @"Abc";
                string = [string lowercaseString];
                string = [string stringByAppendingString:@"xyz"];
                NSLog(@"%@", string);
        }
}
 
 
 
 
 


以上是关于🔥🔥造成循环引用和内存泄漏的几种情况的主要内容,如果未能解决你的问题,请参考以下文章

🔥iOS中野指针问题叙述

🔥🔥如何令自己所写的对象具有拷贝功能?

🔥🔥谈谈 iOS 中图片的解压缩

🔥isKindOfClass 和 isMemberOfClass

Git入门图文教程(1.5W字40图)🔥🔥--深入浅出图文并茂

🔥谈谈事件的产生和传递和响应