iOS | 小心NSTimer中的循环引用
2017-08-11 14:50
253 查看
NSTimer大家都很熟悉,觉得用起来也很简单.然而,由NSTimer引起的循环引用,不经历过一次,一般很难查.
下面看一段代码:
上面的代码主要是利用定时器重复执行p_doSomeThing方法,在合适的时候调用p_stopDoSomeThing方法使定时器失效.
能看出问题吗?在开始讨论上面代码问题之前,需要对NSTimer做一点说明.NSTimer的
scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:
方法的最后一个参数为YES时,NSTimer会保留目标对象,等到自身失效才释放.执行完任务后,一次性的定时器会自动失效;重复性的定时器,需要主动调用
了解
创建定时器时,
循环引用
如果能在合适的时候打破循环引用,就不会有问题了.此时有两种选择:
1.控制器不再引用定时器
2.定时器不再保留当前控制器
第一种是不可行的,那么就只有第二种方法了.也就是合适的时候调用
该问题出现的根本原因就是无法确保一定会调用
无数开发者尝试自己做一个简陋而脆弱的系统来实现网络缓存的功能,殊不知 NSURLCache 只要两行代码就能搞定且好上 100 倍。
这里采用block块的方法为NSTimer增加一个分类,具体细节看代码(程序员最好的语言是代码).
在使用中,最需要注意的就是下面这段代码:
2017.1.6日补充:
在iOS10中,新的定时器即使不被引用,也可以正常运行,但是这依然会导致target不能释放,上面的方法依然十分有用。iOS10中,定时器的API新增了block方法,实现原理和这一样,只不过我这里用的是分类,而系统是在原始类中直接添加方法,最终的行为是一致的。
下面看一段代码:
- (void)viewDidLoad { [super viewDidLoad]; _timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(p_doSomeThing) userInfo:nil repeats:YES]; } - (void)p_doSomeThing { // doSomeThing } - (void)p_stopDoSomeThing { [self.timer invalidate]; self.timer = nil; } - (void)dealloc { [self.timer invalidate]; }
上面的代码主要是利用定时器重复执行p_doSomeThing方法,在合适的时候调用p_stopDoSomeThing方法使定时器失效.
能看出问题吗?在开始讨论上面代码问题之前,需要对NSTimer做一点说明.NSTimer的
scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:
方法的最后一个参数为YES时,NSTimer会保留目标对象,等到自身失效才释放.执行完任务后,一次性的定时器会自动失效;重复性的定时器,需要主动调用
invalidate方法才会失效.
了解
scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:最后一个参数含义之后,你发现何处出现了循环引用?
创建定时器时,
当前控制器(创建定时器的那个控制器,为了描述方便,简称当前控制器)引用而定时器了(为什么因引用定时器?后续要用到这个定时器对象),在给定时器添加任务时,定时器保留了
self(当前控制器).这里就出现了循环引用.
循环引用
如果能在合适的时候打破循环引用,就不会有问题了.此时有两种选择:
1.控制器不再引用定时器
2.定时器不再保留当前控制器
第一种是不可行的,那么就只有第二种方法了.也就是合适的时候调用
p_stopDoSomeThing方法.然而,合适的时机很难找到.假如这是一个验证码倒计时程序,你可以在倒计时结束时调用
p_stopDoSomeThing方法.但是你不能确定用户一定会等倒计时结束才返回到上一级页面.或许你想在
dealloc方法中使定时器失效,那你就太天真了.此时定时器还保留着当前控制器,此方法是不可能调用的,因此会出现内存泄漏.或许在倒计时程序中,你可以重写返回方法,先调用
p_stopDoSomeThing再返回,但这不是一个好主意.
该问题出现的根本原因就是无法确保一定会调用
p_stopDoSomeThing方法.针对这一问题,有些人会选择自己实现一个不保留目标对象的定时器.这里,并不打算采用那种从头写起的方法,正如AFN作者所说的
无数开发者尝试自己做一个简陋而脆弱的系统来实现网络缓存的功能,殊不知 NSURLCache 只要两行代码就能搞定且好上 100 倍。
这里采用block块的方法为NSTimer增加一个分类,具体细节看代码(程序员最好的语言是代码).
//.h文件 #import <Foundation/Foundation.h> @interface NSTimer (SGLUnRetain) + (NSTimer *)sgl_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void(^)(NSTimer *timer))block; @end //.m文件 #import "NSTimer+SGLUnRetain.h" @implementation NSTimer (SGLUnRetain) + (NSTimer *)sgl_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block{ return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(sgl_blcokInvoke:) userInfo:[block copy] repeats:repeats]; } + (void)sgl_blcokInvoke:(NSTimer *)timer { void (^block)(NSTimer *timer) = timer.userInfo; if (block) { block(timer); } } @end //控制器.m #import "ViewController.h" #import "NSTimer+SGLUnRetain.h" //定义了一个__weak的self_weak_变量 #define weakifySelf \ __weak __typeof(&*self)weakSelf = self; //局域定义了一个__strong的self指针指向self_weak #define strongifySelf \ __strong __typeof(&*weakSelf)self = weakSelf; @interface ViewController () @property(nonatomic, strong) NSTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; __block NSInteger i = 0; weakifySelf self.timer = [NSTimer sgl_scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer *timer) { strongifySelf [self p_doSomething]; NSLog(@"----------------"); if (i++ > 10) { [timer invalidate]; } }]; } - (void)p_doSomething { } - (void)dealloc { // 务必在当前线程调用invalidate方法,使得Runloop释放对timer的强引用(具体请参阅官方文档) [self.timer invalidate]; } @end
在使用中,最需要注意的就是下面这段代码:
weakifySelf self.timer = [NSTimer sgl_scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer *timer) { strongifySelf [self p_doSomething]; NSLog(@"----------------"); if (i++ > 10) { [timer invalidate]; } }];
weakifySelf宏定义了一个弱引用的
weakSelf,
strongifySelf宏又根据
weakSelf定义了一个强引用的self.这样在block内使用self和其他地方一样.这里的
self和block外的
self指向一样,但是不同的变量.
2017.1.6日补充:
在iOS10中,新的定时器即使不被引用,也可以正常运行,但是这依然会导致target不能释放,上面的方法依然十分有用。iOS10中,定时器的API新增了block方法,实现原理和这一样,只不过我这里用的是分类,而系统是在原始类中直接添加方法,最终的行为是一致的。
相关文章推荐
- iOS 里面 NSTimer 防止 循环引用
- iOS如何巧妙解决NSTimer的循环引用详解
- iOS NSTimer循环引用的几种解决办法
- ios NSTimer引起的循环引用,以及NSTimer的使用
- iOS容易造成循环引用的三种场景NSTimer以及对应的使用方法(一)
- iOS - 关于NSTimer的循环引用
- iOS NSTimer循环引用的办法
- iOS-NSTimer-pause-暂停-引用循环
- 如何在 iOS 中解决循环引用的问题
- IOS闭包循环引用(Swift)
- ios 避免self循环引用的方法
- iOS容易造成循环引用的三种场景
- iOS 解决block中self的循环引用问题
- ios中关于NSString 的retainCount和循环引用的处理方式
- [转]iOS容易造成循环引用的三种场景,就在你我身边!
- 如何在 iOS 中解决循环引用的问题
- iOS复习笔记7:循环引用问题
- IOS开发---OC语言-㉓类的循环引用
- iOS中造成循环引用的集中情况
- iOS中wkwebView内存泄漏与循环引用问题详解