重新認(rèn)識 NSRunLoopCommonModes

最近寫一些關(guān)于線程保活/取消的測試
具體代碼如下

-(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]];
    }
}

關(guān)于 Runloop 的介紹我也不在此贅述。就說一下我遇到的問題.

[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

這句中,我嘗試著使用了 NSRunLoopCommonModes,來設(shè)定 Mode。就行 ruanloop 運行。
但是我會收到 Crash.

image

但是如果我使用 NSDefaultRunLoopMode 來填充這個 mode 就不會 crash.

image

那么這是為啥?

我的環(huán)境
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 調(diào)用了 CoreFoundation 中的 CFRunLoopAddTimer(_rl, (CFRunLoopTimerRef)timer, (CFStringRef)mode);
  • 最后來到 CoreFoundation 中
image
  • Time 是對 mode 就行了特殊處理,然后添加到 rl->_commonModeItems 中,最終添加到新的 Commond 中
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);

所以為什么 time 使用 Common 卻可以,因為是因為對 Common 對了轉(zhuǎn)換,做了特殊處理。

那么,在來結(jié)合源碼看看

- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate

方法。

他最終會走到 Foundation 中的 moreWork = CFRunLoopRunInMode((CFStringRef)mode, t, true) == kCFRunLoopRunHandledSource;方法中。

image

當(dāng)返回值是 kCFRunLoopRunHandledSource 時,返回一個 true,來完成此次運行。

隨便在說一下這兩個方法,他們?yōu)槭裁床粫顺觥?/p>

  1. -(void)run;
  2. -(void)runUntilDate:(NSDate *)limitDate;
image

原因是:他們會一直循環(huán),所以無法退出的.

接上,那么底層的 CoreFoundation 會對于傳入不同的 mode 做特殊處理嗎?
帶著問題我翻看源碼。

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
image

其實內(nèi)部并沒有做什么特殊處理,就是一系列判斷。

那么為什么設(shè)置為 Common 會 crash 了?

我有找了很多資料。

在一片文章中找到了如下內(nèi)容:

  • 讓 Run Loop 運行在 NSRunLoopCommonModes下是沒有意義的,因為一個時刻 Run Loop 只能運行在一個特定模式下,而不可能是個模式集合
  • 注意Run Loop不能在運行在NSRunLoopCommonModes模式,因為NSRunLoopCommonModes其實是個模式集合,而不是一個具體的模式

所以會掛的原因是因為: Common是一個模式合集,而非一個具體的模式,在這里需要的是一個具體的模式,所以就會 crash.

另外:

CFRunLoopRun(); // 啟動
CFRunLoopStop(CFRunLoopGetCurrent()); // 停止

這兩個是可以啟動和停止的.從源碼來看

image
  • 從源碼看,他的判斷條件是遇到了 Stopped 和 Finished 就會停止

停止方法,最終會設(shè)置停止值。

image
image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容