RunLoop-线程保活
01
AFNetWorking,它既是使用runloop来控制线程的生命周期,让子线程一直停留在内存当中,这样在子线程经常要做事情的比较好
我们可以自己开启一个子线程,然后手动打开里面的runloop,注意runloop的mode里面如果没有任何的source0,source1,timer,observer,他会立即销毁
port就相当于source1
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
MyThread *thread = [[MyThread alloc] initWithTarget:self selector:@selector(doSomeThing) object:nil];
[thread start];
}
- (void)doSomeThing
{
NSLog(@"doSomeThing");
//这里相当于给他添加了一个source1
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSRunLoopCommonModes];
//开启runloop
[[NSRunLoop currentRunLoop] run];
}
02
- (void)viewDidLoad
{
[super viewDidLoad];
self.myThread = [[MyThread alloc] initWithTarget:self selector:@selector(doSomeThing) object:nil];
[self.myThread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//让一些操作在我们自己线程中运行
//waitUntilDone:如果为YES,后面的代码就会等前面的doOtherThing码执行完毕之后才执行后面的next
[self performSelector:@selector(doOtherThing) onThread:self.myThread withObject:nil waitUntilDone:NO];
NSLog(@"next ");
NSLog(@"%@", [NSThread currentThread]);
}
- (void)doOtherThing
{//具体做的事情
NSLog(@"doSomeThing");
NSLog(@"%@", [NSThread currentThread]);
}
- (void)doSomeThing
{//为了线程保活
NSLog(@"doSomeThing");
NSLog(@"%@", [NSThread currentThread]);
//这里相当于给他添加了一个source1
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSRunLoopCommonModes];
//开启runloop
[[NSRunLoop currentRunLoop] run];
}
03
线程一致保活可能存在一定的细节问题,他会影响控制器的生命周期,导致控制器无法被销毁。 线程内部对控制器强引用,用block的方式执行任务,这样控制器是可以销毁,但是线程依旧没有销毁。我们要将线程控制的更加精准一点,可以吧runloop停掉,来销毁线程,他也有方法CFRunLoopStop(CFRunLoopGetCurrent)
04
因为run方法的调用还是不能直接销毁线程,
因为run方法开始的是无限的循环,所以是停不掉的,因为我们只能停止一次,所以创建的时候不要调用run。我们可以自己首先runMode:
- 简单实用
- (IBAction)click:(UIButton *)sender {
// NSLog(@"调用了系统的方法");
[self performSelector:@selector(deallocMythread) onThread:self.myThread withObject:nil waitUntilDone:NO];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// self.myThread = [[MyThread alloc] initWithTarget:self selector:@selector(doSomeThing) object:nil];
self.isStop = NO;
__weak typeof(self) weakSelf = self;
self.myThread = [[MyThread alloc] initWithBlock:^{
//为了线程保活
NSLog(@"doSomeThing-beign");
NSLog(@"%@", [NSThread currentThread]);
//这里相当于给他添加了一个source1
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//开启runloop ,使用run直接开始的是一个无法停止的无限循环,所以我们要自己开启一个
while (!weakSelf.isStop)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"dosometing End");
}];
[self.myThread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//让一些操作在我们自己线程中运行
//waitUntilDone:如果为YES,后面的代码就会等前面的doOtherThing码执行完毕之后才执行后面的next
[self performSelector:@selector(doOtherThing) onThread:self.myThread withObject:nil waitUntilDone:NO];
NSLog(@"next");
}
- (void)doOtherThing
{//具体做的事情
NSLog(@"doOtherThing");
NSLog(@"%@", [NSThread currentThread]);
}
-(void)deallocMythread
{
//这个方法一定要在正确的线程中执行,
self.isStop = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)dealloc
{
//这里是主线程,所以不可以在这直接掉Stop,不然他停掉的是主线
[self performSelector:@selector(deallocMythread) onThread:self.myThread withObject:nil waitUntilDone:NO];
self.myThread = nil;
}
答疑
给人感觉只使用标记就可以控制,是不对的,因为线程到runMode那里停到了,一致在那里休眠,就算你改了YES 代码还是卡在那里
05
为什么停止runloop的时候 用点击按钮可以,但是在dealloc里就不行?
因为在dealloc里面调用的时候,控制器正在销毁,我们这时候执行 [self performSelector:@selector(deallocMythread) onThread:self.myThread withObject:nil waitUntilDone:NO];来进行停止,最后那个参数为NO,说明主线程的销毁,不用等子线程执行完,就执行销毁操作。但是子线程还没有执行完,而且这个方法是由self来调用的,这样就找成了self已经销毁,我还用它发消息肯定有问题。因为runloop在处理主线程到子线程的调用,所以他那个代码会出坏内存访问(控制器坏了)
可以[self performSelector:@selector(deallocMythread) onThread:self.myThread withObject:nil waitUntilDone:YES];//代表子线程的代码执行完毕后,这个代码才往下执行,这样虽然解决了,坏内存访问,但是runloop没有停止成功。通过执行端点我们发现,他又到while里面执行代码了,因为我们转的weakSelf变成了null。当我们停掉了之前的runloop之后,self被销毁了,runloop就判断外面的循环是不是成立,因为weakSelf是一个弱指针,所以在他指向的对象销毁后,他会执行nil,这时候我们是取反,所以条件又成立,又会在开始一个runloop,可以加上(weakSelf && !weakSwlf.isStop)
线程保活06
使用线程的时候注意判断 if(!self.myThread)return;