iOS UI 操作在主线程不一定安全?

问题

最近在看SDWebImage的时候看到了他如何强行保护 UI 操作放置在主线程中执行,代码如下:

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
#endif

顿时心生疑问,按照我自己的写法,不应该这样么:

if ([NSThread isMainThread]) {
       block();
} else {
   dispatch_async(dispatch_get_main_queue(), ^{
       block();
   })
}

在查阅一阵子之后,没想到居然是真的。。。在 ReactiveCocoa 的一个 issue里提到在MapKit 中的 MKMapView 有个 addOverlay 方法,这个方法不仅要在主线程中执行,而且要把这个操作加到主队列中才可以。并且后来 Apple DTS 也承认了这是一个bug

ADTS Bug

由此,我们可以大胆的猜测,苹果的API可能是问题的,我们得想一个更加安全的方式规避这种即使有此类bug,万一别的API也有这样的问题也不至于导致APP出问题

解决方案

我们知道,在主队列中的任务,一定会放到主线程执行; 所以只要是在主队列中的任务,既可以保证在主队列,也可以保证在主线程中执行。所以咱们就可以通过判断当前队列是不是主队列来代替判断当前执行任务的线程是否是主线程,这样更加安全!

方案一:
我们知道在使用 GCD 创建一个 queue 的时候会指定 queue_label,可以理解为队列名,就像下面:

dispatch_queue_t myQueue = dispatch_queue_create("com.apple.threadQueue", DISPATCH_QUEUE_SERIAL);

而第一个参数就是 queue_label,根据官方文档解释,这个queueLabel应该是唯一的,所以SD就采用了这个方式

   //取得当前队列的队列名
   dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)
   
   //取得主队列的队列名
   dispatch_queue_get_label(dispatch_get_main_queue())
   
   然后通过 strcmp 函数进行比较,如果为0 则证明当前队列就是主队列。

正常情况下是可以这样来判断当前队列是不是主队列的,但考虑下面一种情况


//我定义了一个和主队列一样队列名的队列,通过这个判断,很明显判断成了主队列,于是我在里面做UI操作。

- (void)testQueue {
    //获取主队列名
    const char *main_queue_name = dispatch_queue_get_label(dispatch_get_main_queue());
    NSLog(@"\nmain_queue_name====%s", main_queue_name);
    //创建一个和主队列名字一样的串行队列
    dispatch_queue_t customSerialQueue = dispatch_queue_create(main_queue_name, DISPATCH_QUEUE_SERIAL);
    if (strcmp(dispatch_queue_get_label(customSerialQueue), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {
        //名字一样
        NSLog(@"\ncutomSerialQueue is main queue");
        dispatch_async(customSerialQueue, ^{
            //将更新UI的操作放到这个队列
            if ([NSThread isMainThread]) {
                NSLog(@"i am mainThread ");
            }
            UIImageView *imageView = [[UIImageView alloc] init];
            [self.view addSubview:imageView];
            self.view.frame = CGRectMake(0, 0, 50, 50);
            self.view.backgroundColor = [UIColor greenColor];
            NSLog(@"\nUI Action Finished");
        });
        
    } else {
        //名字不一样
        NSLog(@"cutomSerialQueue is main queue");
    }
}

所以,我想表达,如果我定义了一个这样的队列,并且当前队列就是这个队列,然后我再把 SD 设置图片的操作加到这个队列里面,这样会不会导致 SD 误判了,导致程序出问题,虽然这样很极限。如果是我理解错了,还请大神们悉心提出,求轻喷~~~~

执行结果:


result

方案二
采用 dispatch_queue_set_specificdispatch_get_specific 这一组方法为队列绑定标记,后面再取标记对比;当然你在这样的情况下,就别把自己的队列也和主队列打同样的标记了,不然就是在搞事情了。。。。

// 通过设置key/value数据与指定的queue进行关联。
dispatch_queue_set_specific(dispatch_queue_t queue, const void *key,
void *_Nullable context, dispatch_function_t _Nullable destructor);
//参数:
queue:需要关联的queue,不允许传入NULL。
key:唯一的关键字。
context:要关联的内容,可以为NULL。
destructor:释放context的函数,当新的context被设置时,destructor会被调用

// 根据唯一的key取出当前queue的context,如果当前queue没有key对应的context,则去queue的target queue取,取不着返回NULL,如果对全局队列取,也会返回NULL。
dispatch_get_specific(const void *key)

//参数
key:当时设置的关键字。

使用方式:

- (void)function {
    static void *mainQueueKey = "mainQueueKey";
    dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, &mainQueueKey, NULL);
    if (dispatch_get_specific(mainQueueKey)) {
        // do something in main queue
        //通过这样判断,就可以真正保证(我们在不主动搞事的情况下),任务一定是放在主队列中的
    } else {
        // do something in other queue
    }
}

总结

本文就从阅读 SD 源码中的一段代码而引发出的对主队列,主线程的一些思考;如有不正确,还请大神指导,轻喷~~

参考资料

主线程中也不绝对安全的 UI 操作
GCD's Main Queue vs. Main Thread

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

推荐阅读更多精彩内容