iOS開發(fā)-阻塞主線程實現(xiàn)

原文:橘子不酸丶
轉(zhuǎn)載:https://juejin.im/post/5ea283cff265da480d6191b0

前言

最近在項目開發(fā)中遇到需要阻塞主線程的開發(fā)場景,記錄下來過程苛吱,以及在此過程中的理解皮璧。

一充边、場景

在使用WKWebView獲取UserAgent時橱野,需要同步獲取到UA术幔,然而WKWebView的evaluateJavaScript:方法又是異步的椅寺,因此就需要阻塞主線程,等待獲取到UA之后再往下繼續(xù)執(zhí)行兢哭。

先上代碼方便理解。

+ (NSString *)getUserAgent {
    if (userAgentStr) {
        return userAgentStr;
    }
    uaWebView = [[WKWebView alloc] initWithFrame:CGRectZero];
    [uaWebView loadHTMLString:@"<html></html>" baseURL:nil];
    __block BOOL end = NO;
    [uaWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
        userAgentStr = obj;
        end = YES;
    }];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
        end = YES;
    });
    while (!end) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    if (userAgentStr == nil) {
        userAgentStr = @"Mozilla/5.0 (iPhone; CPU iPhone OS 12_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148";
    }
    
    return userAgentStr;
}

首先要明確WKWebView的創(chuàng)建以及執(zhí)行JS的方法evaluateJavaScript:都必須要在主線程操作夫嗓,因此這里并不能dispatch到子線程來處理迟螺。

其次我們來看一下 evaluateJavaScript:completionHandler: 的執(zhí)行過程以及回調(diào)。

image

通過堆棧我們可以看到舍咖,因為WKWebView是單獨的進程處理矩父,所以這里涉及到了進程之間的通信;我們的主線程在需要執(zhí)行evaluateJavaScript:時會調(diào)度到WKWebView的進程來執(zhí)行并獲取到結(jié)果排霉,之后再通過IPC進程間通信回調(diào)給我們app的主線程來處理結(jié)果窍株。

如果我想等待獲取到UA之后再繼續(xù)往下執(zhí)行,這時就需要阻塞主線程了攻柠;首先dispatch_semaphore球订、dispatch_group是不行的,因為這里在evaluateJavaScript的前后都是在我們的主線程瑰钮,因此一旦加鎖就會造成死鎖冒滩。while(YES) {} ?也是不行的,這樣會占滿CPU并且同樣會死鎖浪谴。

此時RunLoop的 runModel:beforeDate: 就發(fā)揮了作用开睡。

二、runModel:beforeDate:

首先來看一下該方法的官方注釋:

Summary

Runs the loop once, blocking for input in the specified mode until a given date.

Declaration

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

Discussion

If no input sources or timers are attached to the run loop, this method exits immediately and returns NO; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop does not guarantee that the run loop will exit immediately. macOS may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

Note

A timer is not considered an input source and may fire multiple times while waiting for this method to return

Parameters

mode
The mode in which to run. You may specify custom modes or use one of the modes listed in Run Loop Modes.

limitDate
The date until which to block.

Returns

YES if the run loop ran and processed an input source or if the specified timeout value was reached; otherwise, NO if the run loop could not be started.

運行runLoop 一次苟耻,阻塞當(dāng)前線程以等待處理一次輸入源篇恒。在處理了一次到達的輸入源或設(shè)定的beforeDate到時間后,runLoop 會 exit凶杖。

其實每一個app啟動后都會開啟RunLoop通過run方法開啟runloop循環(huán)胁艰。通過RunLoop的run方法注釋我們可以看到run方法是通過循環(huán)重復(fù)調(diào)用runMode:beforeDate:來實現(xiàn)的。

我們再看一下React中的runloop開啟

- (void)main {
  @autoreleasepool {
    _runLoop = [NSRunLoop currentRunLoop];
    dispatch_group_leave(_waitGroup);

    NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:self selector:@selector(step) userInfo:nil repeats:NO];
    [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode];

    while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { }
    assert(NO);
  }
}

因此再回到我們我們的場景中就可以理解了智蝠。通過

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

while(!end)我們在這里接管了外邊的runloop處理事件蝗茁,如果有input source就處理input source并返回NO,然后就掛起等待寻咒。直到evaluateJavaScript:completionHandler:的回調(diào)之后end狀態(tài)被修改走出循環(huán)哮翘,繼續(xù)執(zhí)行。

需要注意這里的evaluateJavaScript:completionHandler:和dispatch_after都會作為input source來處理的毛秘。因此處理完input source之后狀態(tài)被改變就走出了while循環(huán)饭寺。繼續(xù)原來的方法繼續(xù)執(zhí)行阻课。

結(jié)語

以上只是本猿對RunLoop冰山一角的小理解。CFRunLoop的源碼也是開源的艰匙,也有YYKit作者的深入理解runloop對RunLoop的理解應(yīng)用限煞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市员凝,隨后出現(xiàn)的幾起案子署驻,更是在濱河造成了極大的恐慌,老刑警劉巖健霹,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旺上,死亡現(xiàn)場離奇詭異,居然都是意外死亡糖埋,警方通過查閱死者的電腦和手機宣吱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞳别,“玉大人征候,你說我怎么就攤上這事∷盍玻” “怎么了疤坝?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長馆铁。 經(jīng)常有香客問我卒煞,道長,這世上最難降的妖魔是什么叼架? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任畔裕,我火速辦了婚禮,結(jié)果婚禮上乖订,老公的妹妹穿的比我還像新娘扮饶。我一直安慰自己,他們只是感情好乍构,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布甜无。 她就那樣靜靜地躺著,像睡著了一般哥遮。 火紅的嫁衣襯著肌膚如雪岂丘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天眠饮,我揣著相機與錄音奥帘,去河邊找鬼。 笑死仪召,一個胖子當(dāng)著我的面吹牛寨蹋,可吹牛的內(nèi)容都是我干的松蒜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼已旧,長吁一口氣:“原來是場噩夢啊……” “哼秸苗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起运褪,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤惊楼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秸讹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體檀咙,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年嗦枢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屯断。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡文虏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出殖演,到底是詐尸還是另有隱情氧秘,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布趴久,位于F島的核電站丸相,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏彼棍。R本人自食惡果不足惜灭忠,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望座硕。 院中可真熱鬧弛作,春花似錦、人聲如沸华匾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜘拉。三九已至萨西,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旭旭,已是汗流浹背谎脯。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留持寄,地道東北人穿肄。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓年局,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咸产。 傳聞我的和親對象是個殘疾皇子矢否,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容