我们经常在子线程中处理一些非UI的任务,我们知道子线程处理完任务后就会自动销毁,假设某个界面需要经常在子线程中处理事情,那么线程的不断创建,销毁也是消耗性能的,因此需求就来了:
如何设计一个子线程,使其命可以一直不死?
1.自定义一个线程继承自NSThread,重写dealloc方法,这样我们监听线程的是否挂掉
@interface DXThread : NSThread
@end
- (void)dealloc{
NSLog(@"%s", __func__);
}
2.把自定义的线程,声明为属性,那样不就可以保命了吗?
#import "DXThread.h"
@interface ViewController ()
@property (strong, nonatomic) DXThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[DXThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
启动程序,打印如下,发现并没有打印dealloc方法,也就说线程没有挂掉,这样的确是保命了
[ViewController test] <DXThread: 0x60000081c100>{number = 3, name = (null)}
然后发现点击屏幕,没有任何打印,说明这个线程已经不能做事情了
如何给线程保命?然后还可以让线程处理事情?
self.thread = [[DXThread alloc] initWithTarget:self selector:@selector(saveThreadLife) object:nil];
[self.thread start];
// 这个方法的目的:线程保活
- (void)saveThreadLife {
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%s ----end----", __func__);
}
代码如上,
- 1.往RunLoop里面添加Source\Timer\Observer(要创建RunLoop,当然RunLoop不需要显示的创建,获取RunLoop的时候如果发现没有默认会创建)
- 2.执行run方法启动RunLoop
事实上,下面这句代码会产生循环引用
self.thread = [[DXThread alloc] initWithTarget:self selector:@selector(saveThreadLife) object:nil];
[self.thread start];
线程会强引用持有controller,controller也会强引用线程,控制器不会调用dealloc方法
可以使用以下方法initWithBlock
self.thread = [[DXThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
NSLog(@"%@----end----", [NSThread currentThread]);
}];
好了现在,控制器pop时候,controller是有销毁了,但是线程任然没有销毁 那么问题来了
controller都销毁了,为什么线程没有挂掉?
我们知道,任务做完,线程就会自动销毁
// 线程一直在执行这个任务,任务不结束,线程怎么可能销毁呢?
[[NSRunLoop currentRunLoop] run];
进入run方法,查看它的介绍
it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:.
In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers
在defaultRunLoopMode下,重复调用runMode:beforeDate:
相当于如下代码
while (1) {
// [NSDate distantFuture] 很遥远,可能是几百年之后
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
换言之,它的适用场景是专门用于开启一个永不销毁的线程,不能直接用run
方法
所以,最终我们解决方案是,自定义一个标记,自定义一个循环,是否要停止线程
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
__weak typeof(self) weakSelf = self;
while (!weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
停止线程
- (IBAction)stop {
// 在子线程调用stop
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 用于停止子线程的RunLoop
- (void)stopThread{
// 设置标记为YES
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
如果想要控制器dealloc,线程也挂掉,那么需要在控制器的dealloc
- (void)dealloc{
NSLog(@"%s", __func__);
[self stop];
}
结果运行,发现pop控制器的时候就崩溃了
因为这行代码设置了NO,
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
不等任务结束,直接执行后面的代码(dealloc执行完大括号,那么控制器一定挂了),那么控制器就销毁了,self == nil
所以while循环中(!weakSelf.isStoped) == !false == true
,由于代码崩溃在这行
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
查看代码执行顺序,发现崩溃时候,停止线程的方法stopThread
并没有来得及执行,就崩溃了
由于给nil发消息不会崩溃,所以我大胆猜测,这个 [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
这个导致[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
方法崩溃,具体原因也不清楚啊
因此,要设置waitUntilDone:YES
,串行执行代码,这样控制器还没彻底挂掉之前,先停止线程
最终while条件是
(weakSelf.isStoped && weakSelf != nil)