RunLoop六:在实际开发中的应用 之 控制线程生命周期(线程保活) 二

xiaoxiao2025-07-30  25

八、 停止 NSRunLoop 运行 上章提到了 ,只有控制器释放了。线程没有被释放。这是因为 代码 卡在了 [[NSRunLoop currentRunLoop] run];这句代码.

任务执行完成后,线程会销毁。但是 有 run 方法的话。代表系统一直在执行run 方法。所以任务并没有执行完成 。

也就是任务没有执行结束,self.thread 线程并不会销毁。

[[NSRunLoop currentRunLoop] run]; 会让线程一直运行。这就会引出问题。

self.thread线程属于控制器的一个属性。控制器死亡,那线程也应该死亡。除非 self.thread 线程全项目都在使用。别的控制器在这个线程中也可以做事情。

如果希望能够控制 NSRunLoop 的声明周期,比如:想让NSRunLoop 死,那 NSRunLoop 就死。这样就需要 修改 一下代码。

让线程活下来,调用 start 方法即可。但是如果是死亡呢?

想让线程跟随控制器的生命周期。那就需要在 dealloc 方法中写 让 线程 死亡的方法。 这样就可以让 runLoop 死亡。就可以打印 initWithBlock 方法中的NSLog(@"---end---");.就可以说线程已经死亡了。

下面的代码正确么? * 在ViewController控制器中的 - (void) dealloc 方法 写 CFRunLoopStop(CFRunLoopGetCurrent());。 * 是错误的写法。因为ViewController 的 dealloc 方法默认是在主线程里面调用的。所有下方图片的写法是在停止主线程的RunLoop。而不是停止 self.thread 线程的RunLoop。

可以在新创建一个方法,比如 - (void) stop 方法,在这个方法中写 CFRunLoopStop(CFRunLoopGetCurrent());。

// 用于停止子线程的RunLoop - (void)stop { // 停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s %@", __func__, [NSThread currentThread]); } 让 stop 方法 在 self.thread 线程中调用即可。 - (void)dealloc { NSLog(@"%s", __func__); // 在子线程调用stop [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:NO]; }

运行代码。可以看到 调用了 stop 方法。可以看到里面的 NSLog 打印了。但是 runLoop 并没有停止。因为 没有打印 initWithBlock 中的 NSLog(@"— end —"); 这句代码。

那可能会 想 是不是 因为控制器已经要销毁了。你在快销毁的时候才执行是不是来不及调用 ?

针对这个问题。修改下界面。

在 橘色界面创建一个 button ,在button 的点击方法写:

- (IBAction)stop { // button 点击方法 // 在子线程调用stop // 这个方法是在主线程 调用的。 // 而 stopThread 方法是在子线程调用的 [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO]; } // 用于停止子线程的RunLoop - (void)stopThread { // button 点击方法 中调用的方法 // 停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s %@", __func__, [NSThread currentThread]); } 运行代码。可以看到 调用了 stopThread 方法。因为里面的 NSLog 已经打印了。但是 runLoop 并没有停止。因为 没有打印 initWithBlock方法中的 NSLog(@"— end —"); 这句代码。

九、RunLoop 中的 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解释第一句:在 NSDefaultRunLoopMode 模式下跑起来,并重复调用 runMode:beforeDate:方法。相当于 run 方法的底层一直在重复调用 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate: nil ] 方法解释第二句:换句话说:run方法的作用就是开启一个无限的循环(也就是不会死掉的循环)。相当于写了一个死循环 while (1).而 在 self.thread 线程调用的 CFRunLoopStop(CFRunLoopGetCurrent());方法,不是停止 run 方法。而是 停止run方法里面的一次循环(当前的runLoop)。因为 run 方法 不能死亡,所以最好还是 自己实现一个 循环。NSRunLoop 的 run 方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)

十、自己实现 循环 (一)、创建 runloop

现在是要把 RunLoop 跑起来,可以使用这句代码: [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow:5] ]

[NSDate dateWithTimeIntervalSinceNow:5] 代表从当前时间在加上5秒。例如:当前时间是 9点16分30秒,这句话就是在 9点16分35秒的时候过时。

如果RunLoop 开始休眠,休眠到 35秒的时候,RunLoop 会自动退出。

当我们希望runloop 不要退出,那就给beforeDate 传一个不会过期的时间. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];.

[NSDate distantFuture] : 遥远的未来。

(二)、有问题的外循环while(1)

while (1) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }

现在的代码是这样的。

需要改的地方是 while(1).

while括号里面不要用1。如果用1 ,那么当调用 - (void)stopThread方法停掉 当前 runloop 。就又会 重新开启一个线程。

那如果把 while(1){… … } 循环去掉。只保留 它里面的代码。可不可以 ?代码如下:

运行程序。可以看到线程启动打印的 initWithBlock 里面的 ---- begin ----

点击 橘色界面的橘色区域。可以看到,执行了 [ViewController test]方法。在3线程。但执行玩这个方法后,就调用了[[WYTread alloc] initWithBlock: 方法中的 NSLog(@"%@----end----");

可以看到,如果不加 外循环 while() 循环。那么 runloop只能使用一次。使用完后直接退出。

(三)、如何添加外循环

添加一个标记@property (assign, nonatomic, getter=isStoped) BOOL stopped;设置标记。在viewDidLoad 方法中写 self.stopped = NO;。在 - (void)stopThread 方法中,设置标记 // 设置标记为YES self.stopped = YES;

(四)、全部代码

#import "ViewController.h" #import "WYTread.h" @interface ViewController () @property (strong, nonatomic) WYTread *thread; @property (assign, nonatomic, getter=isStoped) BOOL stopped; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.stopped = NO; self.thread = [[WYTread alloc] initWithBlock:^{ NSLog(@"%@----begin----", [NSThread currentThread]); // 往RunLoop里面添加Source\Timer\Observer [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (!weakSelf.isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"%@----end----", [NSThread currentThread]); }]; [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]); } - (IBAction)stop:(UIButton *)sender { // 在子线程调用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]); } - (void)dealloc { NSLog(@"%s", __func__); } @end

十一、EXC_BAD_ACCESS (code=EXC_I386_GPFLT) 崩溃信息

上面的代码有一个问题。RunLoop是进入到ViewController 后就自动创建的。那最好在这个控制器销毁的时候让runloop也销毁。

也就是 当进入到橘色界面自动开启了 RunLoop 以后,不点击停止,直接点击 back 返回按钮。 也可以让创建的 runloop 销毁。

但现在的问题是,不能够 自动让 runloop 销毁。需要点击停止按钮。

如何实现 让runloop 在控制器销毁的时候也跟着销毁呢?

如果想让 点击 back 返回按钮时 停止。可以在 - (void)dealloc 中 调用 - (IBAction)stop 方法。

运行程序。进入到橘色界面启动了 RunLoop 以后,不点击停止,直接点击 back 返回按钮。 程序会崩溃。

崩溃信息是:Thread 8: EXC_BAD_ACCESS (code=EXC_I386_GPFLT) 意思是:坏内存访问。

为什么会出现 坏内存访问 错误 ?

当执行 - (void)dealloc时,意味着控制器正在销毁当中,控制器即将死亡。这个时候调用 [self stop:nil]; 就会执行 [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO]; 这句代码:这句代码就会去 子线程(self.thread子线程),去执行- (void)stopThread方法中的代码 - (void)stopThread { // 设置标记为YES self.stopped = YES; // 停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s %@", __func__, [NSThread currentThread]); } 当执行 - (void)stopThread 方法中的代码,按理说应该停止 runloop。 那为什么没有停止 runloop 方法,还程序崩溃了?这是由于 [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO]; 方法中的waitUntilDone 为 NO 导致的。waitUntilDone = NO 的含义是 :不等子线程执行完 stopThread 这个方法。例如:下面的代码 .控制器会同时执行这两个代码。 - (IBAction)stop:(UIButton *)sender { // 在子线程调用stop [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO]; NSLog(@"123"); } 如果 waitUntilDone = Yes。代表会到 self.thread 线程中执行 stopThread方法中的代码。然后在回到 stop 方法中执行 NSLog(@"123");这句话。 然后 stop 方法才算运行完毕。当 waitUntilDone = NO 时, 执行完 performSelector:onThread:withObject:waitUntilDone 方法后,就会调用 dealloc 方法。调用 dealloc 方法就代表 控制器已经销毁了。与此同时 self.thread 会通过 self 去 调用 stopThread 方法。 self 代表控制器。但是这个时候的控制器已经销毁了(因为调用了 dealloc)。控制器已经销毁了。还用 控制器去执行 performSelector:onThread:withObject:waitUntilDone 方法。并且还设置 stopped 属性为 YES , 停止 runloop 等操作。肯定会出现报错。报错信息的坏内存访问,是指控制器已经坏掉了。解决办法是 waitUntilDone = YeswaitUntilDone = Yes 代表子线程的代码执行完毕后,stop 方法才会往下走。运行程序。程序不崩溃。但又有新的问题,runloop 没有停掉。因为没有打印[[WYTread alloc] initWithBlock大括号中的 end。

十二、weakSelf 问题

已经设置了 stopped 属性 为 yes,为什么 还会 再次开启 runloop?

我们在while (!weakSelf.isStoped)中打印一下 weakSelf .结果为 null .

也就是说 当 调用了 dealloc 后, weakfSelf 为null,也就是 NO。

while(!weakSelf.isStoped)

while(!NO)

while(YES)

所以还是可以进入到 循环里面。

这个时候需要修改下 循环语句的判断条件即可。

while (weakSelf && !weakSelf.isStoped).

当 weakSelf 为null 时,第一个就为 NO,这语句就不会再次判断 后面的结果。

现在控制器的执行顺序如下图

runLoop 不结束的原因

是不是可以用强指针引用 weakSelf? 代码如下:

运行程序。发现情况更糟糕,连控制器都不销毁了。

这是因为 产生了循环引用。

上面的办法不行,需要修改while循环中的判断 while (weakSelf && !weakSelf.isStoped)

全部代买

// // ViewController.m // RunLoop源码 // // Created by study on 2018/10/19. // Copyright © 2018年 WY. All rights reserved. // #import "ViewController.h" #import "WYTread.h" @interface ViewController () @property (strong, nonatomic) WYTread *thread; @property (assign, nonatomic, getter=isStoped) BOOL stopped; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.stopped = NO; self.thread = [[WYTread alloc] initWithBlock:^{ __strong typeof (weakSelf) strongSelf = weakSelf; NSLog(@"%@----begin----", [NSThread currentThread]); // 往RunLoop里面添加Source\Timer\Observer [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (strongSelf && !strongSelf.isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"%@----end----", [NSThread currentThread]); }]; [self.thread start]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { if (!self.thread) { return; } [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO]; } // 子线程需要执行的任务 - (void)test { NSLog(@"%s %@", __func__, [NSThread currentThread]); } - (IBAction)stop:(UIButton *)sender { if (!self.thread) { return; } // 在子线程调用stop [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES]; } // 用于停止子线程的RunLoop - (void)stopThread { // 设置标记为YES self.stopped = YES; // 停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s %@", __func__, [NSThread currentThread]); // 清空线程 self.thread = nil; } - (void)dealloc { NSLog(@"%s", __func__); [self stop:nil]; } @end
转载请注明原文地址: https://www.6miu.com/read-5034000.html

最新回复(0)