最近写一些关于线程保活/取消的测试
具体代码如下
-(void)test {
for (int i = 0; i < 100000; ++i) {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
[self performSelector:@selector(stopThread) onThread:thread withObject:nil waitUntilDone:YES];
}
}
-(void)stopThread {
CFRunLoopStop(CFRunLoopGetCurrent());
NSThread *thread = [NSThread currentThread];
[thread cancel];
}
-(void)run {
@autoreleasepool {
NSLog(@"current thread = %@", [NSThread currentThread]);
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
if (!port) {
port = [NSPort port];
}
[runLoop addPort:port forMode:NSDefaultRunLoopMode];
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
关于 Runloop 的介绍我也不在此赘述。就说一下我遇到的问题.
在
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
这句中,我尝试着使用了 NSRunLoopCommonModes
,来设定 Mode。就行 ruanloop 运行。
但是我会收到 Crash.
但是如果我使用 NSDefaultRunLoopMode
来填充这个 mode 就不会 crash.
那么这是为啥?
我的环境
Xcode:10.2.1
Mac:macOS Mojave version 10.14.4
iPhone: iOS 12.2 iPhone 8 Plus 模拟器
我的思考
1. 使用 Timer 时,也可以使用 Common,但是依然可以使用。这里为啥不行?
2. Runloop 只能在一个特定的模式下运行,那么 Common 是不是因为包含了多种,所以不能正常使用?(但是 Common 包含了 Default)
带着思考我去翻了源码.先看看 Timer 的流程.
- 跟着源码先到了 Foundation 中.
- Foundation 调用了 CoreFoundation 中的
CFRunLoopAddTimer(_rl, (CFRunLoopTimerRef)timer, (CFStringRef)mode);
- 最后来到 CoreFoundation 中
- Time 是对 mode 就行了特殊处理,然后添加到
rl->_commonModeItems
中,最终添加到新的 Commond 中
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
所以为什么 time 使用 Common 却可以,因为是因为对 Common 对了转换,做了特殊处理。
那么,在来结合源码看看
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate
方法。
他最终会走到 Foundation 中的 moreWork = CFRunLoopRunInMode((CFStringRef)mode, t, true) == kCFRunLoopRunHandledSource;
方法中。
当返回值是 kCFRunLoopRunHandledSource 时,返回一个 true,来完成此次运行。
随便在说一下这两个方法,他们为什么不会退出。
- -(void)run;
- -(void)runUntilDate:(NSDate *)limitDate;
原因是:他们会一直循环,所以无法退出的.
接上,那么底层的 CoreFoundation 会对于传入不同的 mode 做特殊处理吗?
带着问题我翻看源码。
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
其实内部并没有做什么特殊处理,就是一系列判断。
那么为什么设置为 Common 会 crash 了?
我有找了很多资料。
在一片文章中找到了如下内容:
- 让 Run Loop 运行在 NSRunLoopCommonModes下是没有意义的,因为一个时刻 Run Loop 只能运行在一个特定模式下,而不可能是个模式集合
- 注意Run Loop不能在运行在NSRunLoopCommonModes模式,因为NSRunLoopCommonModes其实是个模式集合,而不是一个具体的模式
所以会挂的原因是因为: Common是一个模式合集
,而非一个具体的模式,在这里需要的是一个具体的模式,所以就会 crash.
另外:
CFRunLoopRun(); // 启动
CFRunLoopStop(CFRunLoopGetCurrent()); // 停止
这两个是可以启动和停止的.从源码来看
- 从源码看,他的判断条件是遇到了 Stopped 和 Finished 就会停止
停止方法,最终会设置停止值。