容易造成循环引用的场景
- 实例对象调用block(类调用block 是没问题的,如 UIView 的动画 block)
- 单例调用block (单例也是实例对象,而且对象仅有一份,不销毁)
- cell 调用 block
- timer
首先我们需要先了解什么是循环引用
- 循环引用
简单来说循环引用就是 A持有B B持有C C持有A ,因为相互持有,导致无法单独释放某一类的内存 。我们以Block回调为例[self.singleton.singleBlock = ^(NSString * _Nonnull des) { self.myName = des; };
singleton是实例对象, self 持有 singleton , singleton持有Block, Block持有 self ,也可直接理解为 self 持有 singleton , singleton 持有 self,当self需要释放的时候,singleton是不需要释放的,但是 singleton 持有self 导致 self 不能被释放,因此,self 无法被释放,导致内存泄漏。
其次我们需要了解那些常见的操作会造成内存泄漏,为什么会造成内存泄漏,而我把这些操作大致归为两类
一、Block 回调造成的循环引用
二、NSTimer
一、Block 回调造成的循环引用
网络使用 block 回调强持有self造成的循环引用
现在对于网络的回调大多数采用两种方式回调:delegate & block
而大多数使用AFNetworking的时候,通常采用 block 进行回调,block 强持有 self 从而造成 循环引用,最终导致内存泄漏。其他自定义类使用 block ,和网络一样
UITableViewCell cell 使用block ,为什么cell也会造成循环引用呢,因为self 持有tableview ,而 tableview 对 cell 其实是一种强持有的关系,所以在cell使用block的时候,也会造成循环引用
单例 使用 block 强持有 self
我们都知道单例一旦被创建,整个应用的声明周期都只会创建者一次,而且在应用的寿命周期结束之前是不会被释放的,那么如果单例使用 block 并且 在 block 回调内调用了 self ,那么此 单例 就强持有了 self,而 单例 不会释放,就造成了self无法释放,从而造成内存泄漏
二、NSTimer 强持有self
- NSTimer
timer 为什么会造成内存泄漏,首先我们看 timer 的使用NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
我们发现: timer 的 target 是 self,当timer的repeat=YES时,timer就会对self强持,当页面将要释放的时候,由于 timer 强持有 self 从而导致 self 无法被释放,这就导致了内存泄漏。
注意:如果你觉得 只要在 dealloc 里 去结束 timer ,那你就想错了,因为他们的关系是必须 timer 先将和self 的持有关系去掉, dealloc 才会被调用,所以我们是不能在 dealloc 里 调用timer的 invalidate 方法的
解决办法,使用 __weak typeof(self)weakSelf = self;
弱化self,打破循环引用(必要的时候我们还需要在block内部声明 strongSelf ,为防止weakSelf因为某种原因在block里提前释放使得weakSelf=nil,变成野指针,导致后边再调用weakSelf 造成崩溃)。示例如下
__weak typeof(self)weakSelf = self;
[singleton shareSingleton].singleBlock = ^(NSString * _Nonnull des) {
weakSelf.myName = des;
};
Timer 销毁请看此文:iOS Timer 详解