NSRunLoop
可以使用 NSRunLoop
类中的方法开启一个线程的运行循环,其主要的开启方法有下面 3 种,但实际上前两种方法都是通过反复调用第三种方法实现的。只是第一个没有过期时间限制,而第二个有时间限制,但两种开启方法默认的循环模式都是 NSDefaultRunLoopMode
。
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
直接使用第三种方法开启运行循环,可以指定自定义的模式,当然也可以使用下面的方法。
- (void)acceptInputForMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
开启运行循环后,便会阻断当前线程,其后的代码需要等到运行循环退出后,才会执行。如果在开启循环时,未添加任何事件输入源或定时器,那么循环会立即结束,线程会继续执行。
需要注意的是,直接指定模式的开启方法,在第一次接收到事件源发出的事件后,或者超时后,便会返回,所以对于需要长时间多次执行任务的辅助线程而言,可以根据实时情况重新开启其运行循环。
简单的,可以通过一个全局变量来控制线程所关联的运行循环的重启。
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning &&
[theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
复杂一点的如下,可以通过不同的状态来切换线程不同的运行模式,这样对于不同类别的任务,也可以在同一个线程中互斥的进行。
声明两个运行循环名称以及控制变量:
const NSRunLoopMode kMyMode = @"MyRunLoopMode";
const NSRunLoopMode kYourMode = @"YourRunLoopMode";
@property (nonatomic, strong) NSRunLoopMode currentMode;
@property (nonatomic, assign) BOOL shouldChangeMode;
@property (nonatomic, assign) BOOL shouldKeepThread;
@property (nonatomic, assign) NSTimeInterval valueSecond;
@property (nonatomic, weak) NSThread *thread;
在下面的方法中,创建一个线程,并在线程中创建两个定时器以不同的模式添加到运行循环中。
- (void)runLoopTest {
__block int number = 0;
self.currentMode = kMyMode;
self.shouldChangeMode = NO;
self.shouldKeepThread = YES;
self.valueSecond = 4;
NSThread *thread = [[NSThread alloc]initWithBlock:^{
NSRunLoop *run = [NSRunLoop currentRunLoop];
NSTimer *timer = [[NSTimer alloc]initWithFireDate:[NSDate date]
interval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer fired %d",number++);
NSLog(@"1. %@",[[NSRunLoop currentRunLoop]currentMode]);
}];
[run addTimer:timer forMode:kMyMode];
NSTimer *timer1 = [[NSTimer alloc]initWithFireDate:[NSDate date]
interval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer1 fired %d",number++);
NSLog(@"2. %@",[[NSRunLoop currentRunLoop]currentMode]);
}];
[run addTimer:timer1 forMode:kYourMode];
while (self.shouldKeepThread) {
number = 0;
if (self.shouldChangeMode) {
[self changeMode];
}
[run acceptInputForMode:self.currentMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:self.valueSecond]];
}
NSLog(@"thread block end");
}];
thread.name = @"test thread";
[thread start];
self.thread = thread;
}
- (void)changeMode {
if ([self.currentMode isEqualToString:kMyMode]) {
self.currentMode = kYourMode;
}else {
self.currentMode = kMyMode;
}
}
}
控制事件
- (void)btnStopClick {
self.shouldKeepThread = NO;
}
- (void)btnChangeModeClick {
self.shouldChangeMode = !self.shouldChangeMode;
}
实际上,上面两个控制事件,改变的是状态,并不会直接对运行循环进行改变,即不会立即切换或退出运行循环。如果改写如下,那么运行循环会立即作出相应的切换或退出。
- (void)btnStopClick {
self.shouldKeepThread = NO;
[self performSelector:@selector(stopRunLoop) onThread:self.thread
withObject:nil waitUntilDone:NO modes:@[kMyMode,kYourMode]];
}
- (void)stopRunLoop {
CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);
}
- (void)btnChangeModeClick {
[self performSelector:@selector(changeMode) onThread:self.thread
withObject:nil waitUntilDone:YES modes:@[kMyMode,kYourMode]];
}
实际上,一个线程的运行循环只能在一个模式运行并且只能监听与其相同模式的输入源或定时器,所谓的切换运行模式,只不过是因为当前运行模式在有效时间到期后退出,而后在根据条件开启了一个新的循环。所以,循环模式的切换是最后一刻的条件所决定的,如果条件进行了多次变更,循环模式可能与原来一致。
而对于第二种立刻生效的切换方式,实际上是因为当前运行循环接收到一个处理事件,状态修改后,循环立即退出,而后再次根据条件重新开启循环。