iOS开发-阻塞主线程实现

原文:橘子不酸丶
转载:https://juejin.im/post/5ea283cff265da480d6191b0

前言

最近在项目开发中遇到需要阻塞主线程的开发场景,记录下来过程,以及在此过程中的理解。

一、场景

在使用WKWebView获取UserAgent时,需要同步获取到UA,然而WKWebView的evaluateJavaScript:方法又是异步的,因此就需要阻塞主线程,等待获取到UA之后再往下继续执行。

先上代码方便理解。

+ (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的创建以及执行JS的方法evaluateJavaScript:都必须要在主线程操作,因此这里并不能dispatch到子线程来处理。

其次我们来看一下 evaluateJavaScript:completionHandler: 的执行过程以及回调。

image

通过堆栈我们可以看到,因为WKWebView是单独的进程处理,所以这里涉及到了进程之间的通信;我们的主线程在需要执行evaluateJavaScript:时会调度到WKWebView的进程来执行并获取到结果,之后再通过IPC进程间通信回调给我们app的主线程来处理结果。

如果我想等待获取到UA之后再继续往下执行,这时就需要阻塞主线程了;首先dispatch_semaphore、dispatch_group是不行的,因为这里在evaluateJavaScript的前后都是在我们的主线程,因此一旦加锁就会造成死锁。while(YES) {} ?也是不行的,这样会占满CPU并且同样会死锁。

此时RunLoop的 runModel:beforeDate: 就发挥了作用。

二、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 一次,阻塞当前线程以等待处理一次输入源。在处理了一次到达的输入源或设定的beforeDate到时间后,runLoop 会 exit。

其实每一个app启动后都会开启RunLoop通过run方法开启runloop循环。通过RunLoop的run方法注释我们可以看到run方法是通过循环重复调用runMode:beforeDate:来实现的。

我们再看一下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:的回调之后end状态被修改走出循环,继续执行。

需要注意这里的evaluateJavaScript:completionHandler:和dispatch_after都会作为input source来处理的。因此处理完input source之后状态被改变就走出了while循环。继续原来的方法继续执行。

结语

以上只是本猿对RunLoop冰山一角的小理解。CFRunLoop的源码也是开源的,也有YYKit作者的深入理解runloop对RunLoop的理解应用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容