八、 停止 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]);
}
- (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]);
}
九、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
runMode:beforeDate:
方法。[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate: nil ]
方法CFRunLoopStop(CFRunLoopGetCurrent());
方法,不是停止 run 方法。而是 停止run方法里面的一次循环(当前的runLoop)。十、自己实现 循环
(一)、创建 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 。就又会 重新开启一个线程。
运行程序。可以看到线程启动打印的 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 *)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 返回按钮。 程序会崩溃。
为什么会出现 坏内存访问 错误 ?
- (void)dealloc
时,意味着控制器正在销毁当中,控制器即将死亡。[self stop:nil];
就会执行 [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
这句代码:- (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 导致的。- (IBAction)stop:(UIButton *)sender {
// 在子线程调用stop
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
NSLog(@"123");
}
stopThread
方法中的代码。然后在回到 stop 方法中执行 NSLog(@"123");
这句话。 然后 stop 方法才算运行完毕。performSelector:onThread:withObject:waitUntilDone
方法。并且还设置 stopped 属性为 YES , 停止 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,这语句就不会再次判断 后面的结果。
运行程序。发现情况更糟糕,连控制器都不销毁了。
上面的办法不行,需要修改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 *)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