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

1-434 1-434     2022-12-01     442

关键词:

原文地址:http://www.cnblogs.com/wengzilin/p/4347974.html
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);
        
 
 
 
 
 


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

谈谈事件的产生和传递和响应? ??事件的产生我们知道UIApplication、UIViewController、UIView都是UIResponder的子类,都是可以处理事件的。在发生触摸事件的时候系统会先将该事件交给UIApplication处理,通常UIApplication先将事件交给UIWi... 查看详情

🔥🔥如何令自己所写的对象具有拷贝功能?(代码片段)

实现NSCoping协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议。@protocolNSCopying-(id)copyWithZone:(nullableNSZone*)zone;@end@protocolNSMutableCopying-(id)mutableCopyWithZone:(nullableNSZone*) 查看详情

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

Git是当前最先进、最主流的分布式版本控制系统,免费、开源!核心能力就是版本控制。本文深入浅出、图文并茂的展示了Git的入门系列教程。01、认识一下Git!—简介Git是当前最先进、最主流的分布式版本控制系统,免费、开... 查看详情

🔥uiviewcontroller的生命周期(代码片段)

UIViewController的生命周期#pragmamark---lifecircle//非storyBoard(xib或非xib)都走这个方法-(instancetype)initWithNibName:(NSString*)nibNameOrNilbundle:(NSBundle*)nibBundleOrNilNSLog(@"%s",__FUNCTION__);if(self=[supe 查看详情

🔥界面卡顿的原因?

界面卡顿的原因,界面为什么会卡顿?界面成像原理: 不论是以前的CRT还是现在的液晶显示器,成像原理是一致的;  CPU部分:逻辑的计算;计算好将要显示的内容转交给GPU;GPU部分:GPU开始渲染后将结果换到帧缓冲区,随后视频... 查看详情

🔥如果页面a跳转到页面b,a的viewdiddisappear方法和b的viewdidappear方法哪个先调用?(代码片段)

如果页面A跳转到页面B,A的viewDidDisappear方法和B的viewDidAppear方法哪个先调用?ViewController为一级界面,BViewController和CViewController为两个二级界面ViewController为一级界面,BViewController和CViewController为两个二级界面//我在两个VC里面都... 查看详情

🔥springboot图文教程2—日志的使用「logback」「log4j」

有天上飞的概念,就要有落地的实现概念+代码实现是本文的特点,教程将涵盖完整的图文教程,代码案例文章结尾配套自测面试题,学完技术自我测试更扎实概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一... 查看详情

200行代码实现简版react🔥(代码片段)

200行代码实现简版react??现在(2018年)react在前端开发领域已经越来越??了,我自己也经常在项目中使用react,但是却总是好奇react的底层实现原理,多次尝试阅读react源代码都无法读下去,确实太难了。前不久在网上看到几篇介绍如... 查看详情

项目依赖和生成顺序造成的问题

...发生问题的工程A,引用了工程B,而B工程里也引用了A,造成循环引用。开发人员在B中将引用A去掉了(A中仍引用B)解除了循环引用,但A的依赖项中自动忽略了B,因此导致A和B的默认生成顺序没有关联,系统生成时总是先生成A... 查看详情

block中self会造成循环引用问题

 将代码块中的self换成unsafeSelf __unsafe_unretained与__weak99%相同__weak当对象释放之后会自动设置为nil而__unsafe_unretained不会  查看详情

解决nstimer或cadisplaylink计时器造成的循环引用问题。(代码片段)

众所周知,我们在使用NSTimer或者CADisplayLink的时候,经常会导致引用它们的类不能正常释放,那是因为引用它们的类与它们之间产生了循环引用。看以下代码:self.timer=[NSTimerscheduledTimerWithTimeInterval:2.0target:selfselector:@selector(runTimer... 查看详情

block循环引用(中)(代码片段)

不会造成循环引用的block大部分GCD方法1dispatch_async(dispatch_get_main_queue(),^2[selfdoSomething];3);因为self并没有对GCD的block进行持有,没有形成循环引用。目前我还没碰到使用GCD导致循环引用的场景,如果某种场景self对GCD的block进行了持... 查看详情

nstimer解除循环引用

...常使用的类,却有一个最大的弊病,就是会强引用target。造成调用timer很麻烦。稍有不慎就造成内存泄漏。下面就是为解决问题做的封装。直接上代码:#import<Foundation/Foundation.h>@interface LZLTimer:NSObject-(void)startTimerInterval:(NS... 查看详情

哪些操作会造成内存泄漏?

内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,... 查看详情

智能指针循环引用--转(代码片段)

...ared_ptr1,这样shared_ptr1.count()和shared_ptr2.count()都为1,这就造成了循环引用,循环引用会导致堆内存无法正确释放,导致内存泄露。  考虑一个简单的对象——家长与子女:aParenthasa 查看详情

android面试题:说一下pendingintent和intent的区别

请点赞,你的点赞对我意义重大,满足下我的虚荣心。🔥Hi,我是小彭。本文已收录到GitHub·Android-NoteBook中。这里有Android进阶成长知识体系,有志同道合的朋友,关注公众号[彭旭锐]跟我一起成长。前言从字面意思上理解... 查看详情

爱创课堂每日一题101天-哪些操作会造成内存泄漏?

内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,... 查看详情

计算机基础:今天一次把unicode和utf-8说清楚

请点赞关注,你的支持对我意义重大。🔥Hi,我是小彭。本文已收录到GitHub·Android-NoteBook中。这里有Android进阶成长知识体系,有志同道合的朋友,关注公众号[彭旭锐]带你建立核心竞争力。前言在日常开发过程中,Unicode&a... 查看详情