1、NSTimer失效
1.1、失效原因
添加一个Timer,如果在TableView或其他可滚动的View滚动时,Timer就会暂停执行:
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer working on %@",[NSRunLoop currentRunLoop].currentMode);
}];
打印结果:
~: timer working on kCFRunLoopDefaultMode
原因是RunLoop同一时间只能运行在一种模式下,RunLoop默认是在DefaultMode模式下工作,当拖拽UI时,RunLoop会切换到UITrackingModel模式下工作。
而Timer通过scheduled方式创建,默认会被添加到RunLoop中的Default模式下工作,所以当RunLoop切换到UITracking模式后,Timer会暂停工作。
1.2、解决办法
如果要解决这个问题,需要让Timer在Default和UITracking两种模式下都能工作:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer working on %@",[NSRunLoop currentRunLoop].currentMode);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
打印结果:
~: timer working on kCFRunLoopDefaultMode
~: timer working on UITrackingRunLoopMode
默认Timer会在Default模式下工作,当拖拽ScrollView时,Timer会切换到UITracking模式下工作。
1.3、CommonModes
CommonModes并不是一个真正的模式,它只是一个标记,timer在设置了Common标记后,它就能在RunLoop中的_commonModes数组中的模式下工作。_commonModes包括NSDefaultRunLoopMode和UITrackingRunLoopMode,所以Timer可以在两种模式下运行。
被标记为CommonModes的timer会被保存在RunLoop的_commonModeItems中。
(_commonModes和_commonModeItems参考 RunLoop对象 )
2、线程保活
2.1、保活原因
创建的子线程,默认执行完任务后就会自动销毁,比如:
- (void)viewDidLoad {
[super viewDidLoad];
XLThread *thread = [[XLThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
}
- (void)run {
NSLog(@"%s - %@",__func__, [NSThread currentThread]);
}
打印结果:
~: -[ViewController run] - <XLThread: 0x600000434d80>{number = 7, name = (null)}
~: -[XLThread dealloc]
如果想要自线程能够长时间的存在,就需要想办法保住线程的命(让线程保持激活状态)。
即使使用强指针指向线程对象,能够使线程对象不会被销毁,但是它在执行完任务后就会失效。
2.2、线程保活
子线程的RunLoop默认没有开启,需要手动获取并开启,并在RunLoop中添加Source/Timer/Observer保证RunLoop不退出,这样就可以保持线程不在执行完任务后失效(保持激活状态)。
添加线程,开启RunLoop并添加NSPort对象:
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[XLThread alloc] initWithTarget:self selector:@selector(keepThreadAlive) object:nil];
[self.thread start];
}
// 线程保活逻辑
- (void)keepThreadAlive {
NSLog(@"%s - %@",__func__, [NSThread currentThread]);
// 添加source/timner/observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%s --end--", __func__);
}
子线程处理任务:
- (void)run {
NSLog(@"%s - %@",__func__, [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:YES];
}
2.3、控制线程生命周期
开启线程中的RunLoop后,会保住线程的命,但是RunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程。所以需要自己去仿照run方法,添加一个while循环,和一个结束条件self.stopRunLoop:
- (void)keepThreadAlive {
NSLog(@"%@ start %s", [NSThread currentThread], __func__);
// 添加source/timner/observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// 添加结束循环条件
while (!self.stopRunLoop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%@ end %s", [NSThread currentThread], __func__);
}
停止线程需要调用killThread方法,结束子线程中的runLoop:
// 杀死线程
- (void)killThread {
[self performSelector:@selector(stopCurrentRunLoop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// 停止线程中的RunLoop
- (void)stopCurrentRunLoop {
self.stopRunLoop = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
}
*waitUntilDone:代表是否要等待子线程处理完毕,如果killThread方法是在VC的dealloc方法中调用,这里需要设置成YES,避免VC被提前释放产生坏内存访问。
2.4、线程保活封装
如果想在多个地方使用线程保活可以封装一个自定义的线程,控制线程生命周期的逻辑都封装到内部,外部只调用就可以,具体可参考:XLThread