十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
singleton是实例对象, self 持有 singleton , singleton持有Block, Block持有 self ,也可直接理解为 self 持有 singleton , singleton 持有 self,当self需要释放的时候,singleton是不需要释放的,但是 singleton 持有self 导致 self 不能被释放,因此,self 无法被释放,导致内存泄漏。
成都创新互联公司为企业级客户提高一站式互联网+设计服务,主要包括成都网站建设、成都做网站、成都App定制开发、重庆小程序开发公司、宣传片制作、LOGO设计等,帮助客户快速提升营销能力和企业形象,创新互联各部门都有经验丰富的经验,可以确保每一个作品的质量和创作周期,同时每年都有很多新员工加入,为我们带来大量新的创意。
一、Block 回调造成的循环引用
二、NSTimer 强持有self
解决办法,使用 __weak typeof(self)weakSelf = self; 弱化self,打破循环引用(必要的时候我们还需要在block内部声明 strongSelf ,为防止weakSelf因为某种原因在block里提前释放使得weakSelf=nil,变成野指针,导致后边再调用weakSelf 造成崩溃)。示例如下
Timer 销毁请看此文: iOS Timer 详解
循环引用的实质:多个对象相互之间有强引用,不能释放让系统回收。
如何解决循环引用?
1、避免产生循环引用,通常是将 strong 引用改为 weak 引用。
比如在修饰属性时用weak
在block内调用对象方法时,使用其弱引用,这里可以使用两个宏
#defineWS(weakSelf) __weak __typeof(*self)weakSelf = self;// 弱引用#defineST(strongSelf) __strong __typeof(*self)strongSelf = weakSelf;//使用这个要先声明weakSelf
还可以使用__block来修饰变量
在MRC下,__block不会增加其引用计数,避免了循环引用
在ARC下,__block修饰对象会被强引用,无法避免循环引用,需要手动解除。
2、在合适时机去手动断开循环引用。
通常我们使用第一种。
循环引用场景:
1. 自循环引用
对象强持有的属性同时持有该对象
2. 相互循环引用
3. 多循环引用
1、代理(delegate)循环引用属于相互循环引用
delegate 是iOS中开发中比较常遇到的循环引用,一般在声明delegate的时候都要使用弱引用 weak,或者assign,当然怎么选择使用assign还是weak,MRC的话只能用assign,在ARC的情况下最好使用weak,因为weak修饰的变量在释放后自动指向nil,防止野指针存在
2、NSTimer循环引用属于相互循环使用
在控制器内,创建NSTimer作为其属性,由于定时器创建后也会强引用该控制器对象,那么该对象和定时器就相互循环引用了。
如何解决呢?
这里我们可以使用手动断开循环引用:
如果是不重复定时器,在回调方法里将定时器invalidate并置为nil即可。
如果是重复定时器,在合适的位置将其invalidate并置为nil即可
3、block循环引用
一个简单的例子:
由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用。
解决方案就是使用__weak修饰self即可
并不是所有block都会造成循环引用。
只有被强引用了的block才会产生循环引用
而比如dispatch_async(dispatch_get_main_queue(), ^{}),[UIView animateWithDuration:1 animations:^{}]这些系统方法等
或者block并不是其属性而是临时变量,即栈block
还有一种场景,在block执行开始时self对象还未被释放,而执行过程中,self被释放了,由于是用weak修饰的,那么weakSelf也被释放了,此时在block里访问weakSelf时,就可能会发生错误(向nil对象发消息并不会崩溃,但也没任何效果)。
对于这种场景,应该在block中对 对象使用__strong修饰,使得在block期间对 对象持有,block执行结束后,解除其持有。
1.自循环引用
2.相互循环引用
3.多循环引用
假如有一个对象,内部强持有它的成员变量obj,
若此时我们给obj赋值为原对象时,就是自循环引用。
对象A内部强持有obj,对象B内部强持有obj,
若此时对象A的obj指向对象B,同时对象B中的obj指向对象A,就是相互引用。
假如类中有对象1...对象N,每个对象中都强持有一个obj,
若每个对象的obj都指向下个对象,就产生了多循环引用。
1.代理
2.Block
3.NSTimer
4.大环引用
1.避免产生循环引用。
在使用代理时,两个对象,一个强引用,一个弱引用,避免产生相互循环引用。
2.在合适的时机手动断环。
1.__weak
2.__block
3.__unsafe_unretained 用这个的关键字修饰的对象也没有增加引用计数,和__weak在效果上是等效的。
对象B会强持有A,对象A弱引用B
__block在ARC和MRC中是不同的:
1.修饰对象不会增加其引用计数,避免了循环引用。
2.如果被修饰的对象在某一时机被释放,会产生 悬垂指针 ,再通过这个指针去访问原对象的话,会导致内存泄露,所以一般不建议用,__unsafe_unretained去解除循环引用。
NSTimer
假如VC中有个广告栏,需要1S中滚动一次播放下一个广告,我们会把这个广告栏的UI对象作为VC的成员变量,由VC进行强持有。
因为广告栏每隔1S需要滚动播放,则广告栏中会添加成员变量NSTimer并强引用,当分配定时回调事件之后,NSTimer会对广告栏的Target进行强引用,就产生了相互循环引用。
如果把对象对NSTimer的强引用改为弱引用,是无法解决问题的, 原因如下图:
因为当NSTimer被分配之后,会 被当前线程的Runloop进行强引用 ,
如果对象以及NSTimer是在主线程创建的,就会被主线程的Runloop持有这个NSTimer,所以即使我们在广告栏中通过弱引用来指向NSTimer,但是由于主线程中Runloop常驻内存通过对NSTimer的强引用,再通过NSTimer对对象的强引用,仍然对这个对象产生了强引用,此时即使VC页面退出,去掉VC对对象的引用,当前广告栏仍然有被Runloop的间接强引用持有,这个对象也不会被释放,此时就产生了内存泄露。
解决方法:NSTimer分重复定时器和非重复定时器
在定时器的回调方法中去调用 [NSTimer invalid] 同时将 NSTimer置为nil ,可以将Runloop对NSTimer的强引用解除掉,同时NSTimer也解除了对对象的强引用。
不能在定时器的回调方法中去调用[NSTimer invalid]以及将NSTimer置为nil操作,此时的解决方案是:
左侧是Runloop对NSTimer的强引用,右侧是VC对对象的强引用,
可以在NSTimer和对象中间 添加一个中间对象 ,然后由NSTimer对中间对象进行强引用,
同时中间对象分别对NSTimer和广告栏对象进行弱引用,那么对于重复对象而已,
当当前VC退出之后,VC就释放了对广告栏对象的强引用,
当下次定时器的回调事件回来的时候,可以在中间对象当中,判断当前中间对象所持有的弱引用广告栏对象是否被释放了,
实际上就是判断中间对象当中所持有的weak变量是否为nil,
如果是nil,然后调用[NSTimer invalid]以及将NSTimer置为nil,
就打破了Runloop对NSTimer的强引用以及NSTimer对中间对象的强引用
这个解决方案是利用了:当一个对象被释放后,它的weak指针会自动置为nil。
中间对象TimerWeakObject的实现
-- :表示弱引用。
- :表示强引用。
循环引用可以简单理解为对象A引用了对象B,而对象B又引用了对象A:A - B - A,此时双方都同时保持对方的一个引用,导致任何时候双方的引用计数都不为0,双方始终无法释放就造成内存泄漏。
当然不只是两个对象之间相互引用会形成循环引用,多个对象之间相互引用最终形成环同样会形成循环引用。
例如:A-B-C-....-X-B。
循环引用对 app 有潜在的危害,会使内存消耗过高,导致内存泄漏,性能变差和 app 闪退等。
block 、 delegate 、NSTimer
self.tableView.delegate = self;
如果 delegate使用strong修饰就会构成循环引用:self - tableView - delegate - self。
所以在定义delegate属性时使用weak便能解决这一问题:self - tableView -- delegate - self。tableView和delegate之间不是强引用,所以构不成循环。
规避delegate循环引用的杀手锏也是简单到哭:定义delegate属性时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong。
(1)并不是所有block都会产生循环引用,block是否产生循环引用是需要我们去判断的,例如
(2)self - reachabilityManager - block - self,才会产生循环引用,并且XCode给出了循环引用warning,例如
(3)解决block循环引用的方法是使用__weak修饰self,然后在block里使用被修饰后的weakSelf来代替self:
1、合适的时机启动和销毁 NSTimer
解决 NSTimer 的循环引用,我们首先会想到的方法应该是在 OneViewController dealloc 之前就销毁 NSTimer,这样循环就被打破了。
最简单的方法就是在 viewWillAppear 中启动 NSTimer,然后在 viewWillDisappear 中销毁 NSTimer,成对出现,绝对没有问题。
2、Effective Objective-C ”中的52条方法
计时器保留其目标对象,反复执行任务导致的循环,确实要注意,另外在dealloc的时候,不要忘了调用计时器中的 invalidate方法。
循环引用,指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存。其实有点类似死锁。
举个例子:A-B-C-....-X-B -表示强引用,这样的B的引用计数就是2,假如A被系统释放了,理论上A会自动减小A所引用的资源,就是B,那么这时候B的引用计数就变成了1,所有B无法被释放,然而A已经被释放了,所有B的内存部分就肯定无法再释放再重新利用这部分内存空间了,导致内存泄漏。
情况一:delegate
Delegate是ios中开发中最常遇到的循环引用,一般在声明delegate的时候都要使用弱引用weak或者assign
@property (nonatomic, weak, nullable) id UITableViewDelegate delegate;
当然怎么选择使用assign还是weak,MRC的话只能用assign,在ARC的情况下最好使用weak,因为weak修饰的变量在是否后自动为指向nil,防止不安全的野指针存在
情况二:Block
Block也是比较常见的循环引用问题,在Block中使用了self容易出现循环引用,因此很多人在使用block的时候,加入里面有用到self的操作都会声明一个__weak来修饰self。其实便不是这样的,不是所有使用了Block都会出现Self循环引用问题,只有self拥有Block的强引用才会出现这种情况。
所以一般在函数中临时使用Block是不会出现循环应用的,因为这时候Block引用是属于栈的。当栈上的block释放后,block中对self的引用计数也会减掉
当然不一定要Self对Block有直接的引用才会出现,假如self的变量B,B中有个Block变量,就容易出现这种情况,好的是在block出现循环引用的,xcode7会出现警告提示(之前版本不确定)。
情况三:NSTimer
这是一个神奇的NSTimer,当你创建使用NSTimer的时候,NSTimer会默认对当前self有个强引用,所有在self使用完成打算是否的时候,一定要先使用NSTimer的invalidate来停止是否时间控制对self的引用
[_timer invalidate];
出现循环引用的三种情况:
1.声明代理delegate属性
2.使用block时
3.使用NSTimer的时候
1.代理属性导致循环引用。
解决方案就是一定要记住,在声明delegate的时候修饰为weak(ARC)或者assign(MRC)
ARC环境下
//一代理属性为什么用weak,如果用strong的话会发生循环引用
//self -- person -- delegate -- self
// self.person = [[Person alloc] init];
// self.person.delegate = self;
MRC环境同理。
2.block导致的循环引用
这个比较复杂,我将它单独写在了一篇博文中.
block导致的循环引用问题的分析基解决办法
3.关于NSTimer导致的循环引用,我暂时不做讲解,准备充分时再补上。
NSTimer经常被作为某个类的成员变量,而NSTimer初始化时要制定self为target,容易造成循环引用。另一方面,若timer一直处于validate的状态,则其引用计数将始终大于0.