内存管理系列文章
内存管理—MRC时代的手动内存管理
内存管理—weak的实现原理
内存管理——autorelease原理分析
内存管理——定时器问题
iOS程序的内存布局
CADisplayLink、NSTimer的循环引用问题
CADisplayLink
是QuartzCore
框架下的的一种定时器,用在跟画图相关的处理当中。NSTimer
大家应该很熟悉,是我们最常用的定时器。这两种定时器分别提供如下两个API
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
这两个API里面都有target
参数,该target
会被CADisplayLink/NSTimer
强引用。如果CADisplayLink
或者NSTimer
作为属性被一个视图控制器VC强引用,当我们在调用上述两个API的时候,target
参数传VC,这样VC和CADisplayLink/NSTimer
之间便会形成引用循环,无法释放,造成内存泄漏。图示如下
NSTimer的解决方案1
通过使用别的API来添加NSTimer
,如
(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
并且将self
通过__weak typeof(self) weakSelf == self;
包装成弱指针,传入其中即可。
NSTimer的解决方案2
通过增加一个中间代理对象来打破引用循环。请看下图
如上图所示,在timer
和VC
之间增加一个代理对象otherObject
,timer
的强指针target
指向otherObject
,otherObject
的弱指针target
指向VC
,这样就成功打破了引用循环。我们之所以需要借助第三者来破环,是因为NSTimer
并非开源,我们无法修改其内部target
的强弱性。因此只能通过一个自定义的代理对象来做一层引用中转,最终打破引用循环。
现在还有一个细节需要处理,增加代理对象otherObject
之前,是由timer
通过target
直接调用VC
里面的定时器方法的。现在中间多了一层otherObject
,该如何实现定时器方法的调用呢?其实方法蛮多的,相信大家都能想出一些解决方案。这里就直接推荐一种比较巧妙的方法——通过消息转发。如下图
因为代理对象的本质目的,就是打破引用循环,并且传递方法,了解OC消息机制的原理前提下,你应该很好理解消息转发的作用,正好可以巧妙的用在这个场景下。请好好体会一下。
下面是一份代码案例
#import "ViewController.h"
#import "CLProxy.h"
@interface ViewController ()
//@property (nonatomic, strong) CADisplayLink *link;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//CADisplayLink用来保证调用频率和屏幕的刷帧频率一致,60FPS
// self.link = [CADisplayLink displayLinkWithTarget:[CLProxy proxyWithTarget:self] selector:@selector(linkTest)];
// [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[CLProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];