美文网首页iOS 多线程
iOS UI 操作在主线程不一定安全?

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

作者: Bugfix | 来源:发表于2017-07-14 09:29 被阅读530次

问题

最近在看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

相关文章

  • GCD

    GCD 队列与线程的关系 主队列和主线程 『ios』主线程 和 主队列的关系,绝对安全的UI操作,主线程中一定是主...

  • 主线程中也不绝对安全的 UI 操作

    主线程中也不绝对安全的 UI 操作 主线程中也不绝对安全的 UI 操作

  • iOS拾遗——为什么必须在主线程操作UI

    iOS拾遗——为什么必须在主线程操作UI iOS拾遗——为什么必须在主线程操作UI

  • AsyncTask和线程池

    在Android中,UI操作是线程不安全的,如果想要在子线程中进行UI操作,或者在主线程中进行耗时操作,则需要借助...

  • 学习笔记

    UI操作为什么在主线程? UIKit线程安全性:UIKit并不是一个线程安全的类,UI操作涉及到渲染访问各种Vie...

  • RxSwift 判断是否为“主队列”

    在开发 iOS 的时候,我们都知道 UI 相关的操作必须放在主线程,但是只要放在主线程就安全了么? 答案是否定的。...

  • android线程管理总结

    一 前言: 在android中,UI主线程并非线程安全的,所有UI相关的操作均需在UI主线程中完成。在默认情况下,...

  • iOS 线程与队列之间的关系

    引导问题:UI刷新,为什么需要在主线程中执行? 原因一:UIKit的操作不是线程安全的 在多个线程下进行UI操作,...

  • iOS 异步绘制与显示的工具-YYAsyncLayer源码和原理

    众所周知,在iOS系统中,UI相关操作和用户的操作都是在主线程中进行,大量的UI操作,会造成主线程阻塞,影响应用的...

  • iOS下FMDB的多线程操作(一)

    iOS中一些时间比较长的操作都应该放在子线程中,以避免UI的卡顿。而sqlite 是非线程安全的,故在多线程中不能...

网友评论

    本文标题:iOS UI 操作在主线程不一定安全?

    本文链接:https://www.haomeiwen.com/subject/prrnhxtx.html