问题描述:
在iOS9及之前通过
_timer = [NSTimer scheduledTimerWithTimeInterval:0.05f repeats:YES block:^(NSTimer * _Nonnull timer) {
}];这个方法创建的定时器,在运行的过程中程序会crash
原因分析:
由于ios9以上系统对内存回收机制做了修改,当对象函数运行中对对象本身进行释放时,会到函数运行结束完后才真正释放,ios9以前的系统会在函数运行中立即释放对象,不会等到函数运行结束,如果该函数之后对对象进行操作就会crash。
而通过
_timer = [NSTimer timerWithTimeInterval:0.05f target:self selector:@selector(seekToNewTime) userInfo:nil repeats:YES];这种方式创建的定时器,控制器持有timer,同时定时的target持有当前控制器,造成强引用timer是不会被释放掉的,除非手动讲定时器 [_timer invalidate]; _timer=nil;此时定时器才会被系统释放,注意:使用定时器的时候,在定时器任务结束的时候或者这个控制器或页面销毁的时候一定要做一次上面的处理把定时器释放掉,否则会造成循环引用,导致当前的控制器或者页面无法销毁造成内存泄漏。
补充:
定时器的几种创建方式:
①:+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
②:+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
③:+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))blockAPI_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
④:+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))blockAPI_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
其中方式②会默认把NSTimer以NSDefaultRunLoopMode添加到主Runloop上,而当UI刷新的时候,就不是NSDefaultRunLoopMode了,这样,NSTimer就会停了。NSDefaultRunLoopMode: NSTimer只有在默认模式下(NSDefaultRunLoopMode)工作,切换到其他模式不再工作,比如拖拽了界面上的某个控件(会切换成UITrackingRunLoopMode),注意其中方式③和方式④必须在iOS10.0以上系统使用,上面导致crash的就是iOS9及以下系统不支持该方式创建定时器
可以改成这样:
NSTimer *timer = [NSTimer timerWithTimeInterval:0.1target:selfselector:@selector(scheduledTime) userInfo:nilrepeats:YES]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
注意:如果timer是在子线程开启的,就需要对把timer加入到该线程的runloop中去
关于Runloop的补充:
// 创建一个定时器(NSDefaultRunLoopMode)
NSTimer*timer = [NSTimertimerWithTimeInterval:2.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];
// NSDefaultRunLoopMode:NSTimer只有在默认模式下(NSDefaultRunLoopMode)工作,切换到其他模式不再工作,比如拖拽了界面上的某个控件(会切换成UITrackingRunLoopMode)[[NSRunLoopmainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 创建一个定时器(UITrackingRunLoopMode)
NSTimer*timer = [NSTimertimerWithTimeInterval:2.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];
// 拖拽UI界面时出发定时器,在默认模式(NSDefaultRunLoopMode)下不工作[[NSRunLoopmainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 创建一个定时器(NSRunLoopCommonModes)NSTimer*timer = [NSTimertimerWithTimeInterval:2.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];
// NSRunLoopCommonModes仅仅是标记NSTimer在两种模式(UITrackingRunLoopMode/NSDefaultRunLoopMode)下都能运行,但一个RunLoop中同一时间内只能运行一种模式[[NSRunLoopcurrentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 默认已经添加到主线程中RunLoop(mainRunLoop)中(Mode:NSDefaultRunLoopMode)[NSTimerscheduledTimerWithTimeInterval:2.0target:selfselector:@selector(run) userInfo:nilrepeats:YES];
Runloop的Mode:系统默认注册了5中Mode:
NSDefaultRunLoopMode:默认的Mode,通常主线程的RunLoop是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,当用户与界面交互的时候会在此Mode下运行
NSRunLoopCommonModes:这个不是一种真正的Mode,是一个占位用的Mode
UIInitializationRunLoopMode:程序启动时的Mode,启动完成后就不在此Mode下
GSEventReceiveRunLoopMode:接受系统事件的内部Mode,一般我们用不到
RunLoop的使用