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

image.png

一直想搞清楚主线程和主队列的关系。

其实我一直带着这么几个问题。
1.主线程中的任务一定在主队列中执行吗?
2.如何保证一定在主线程中执行?
3.如何保证既在主线程中执行又在主队列中执行?

下面我们带着这几个问题来看下面的文章。

先来认识这几个方法

//给指定的队列设置标识
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(key)
//获取指定队列的标识
dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);

第一种情况

//给主队列设置标识
    dispatch_queue_set_specific(dispatch_get_main_queue(), key, @"main", NULL);
//放到同步队列中 全局并发队列中
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"main thread: %d", [NSThread isMainThread]);
        // 判断是否是主队列
        void *value = dispatch_get_specific(key);//返回与当前分派队列关联的键的值。
        NSLog(@"main queue: %d", value != NULL);
    });

打印的结果:

main thread: 1 //是主线程
main queue: 0 //不是主队列

分析:不是主队列是因为 在全局并发队列中,但是在全局并发队列中,为何又在主线程执行呢?经过查阅资料发现,苹果是为了性能,所以在主线程执行,线程切换是耗性能的。

第二种情况

//异步加入到全局并发队列中
 dispatch_async(dispatch_get_global_queue(0, 0), ^{
//异步加入到主队列中
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"main thread: %d", [NSThread isMainThread]);
            NSLog(@"%@",[NSThread currentThread]);
            // 判断是否是主队列
            void *value = dispatch_get_specific(key);//返回与当前分派队列关联的键的值。
            NSLog(@"main queue: %d", value != NULL);
        });
    });
    NSLog(@"dispatch_main会堵塞主线程");
    dispatch_main();
    NSLog(@"查看是否堵塞主线程");

打印结果:

dispatch_main会堵塞主线程
main thread: 0  //不是主线程
<NSThread: 0x600000b73b80>{number = 3, name = (null)}//不是主线程
main queue: 1   //是主队列

分析:明明是在dispatch_get_main_queue()中,为何不是在主线程执行呢?是不是很颠覆三观?原因再 dispatch_main()这个函数。这个函数的作用,经过查阅资料和读文档获取:

/*!
 * @function dispatch_main
 *
 * @abstract
 * Execute blocks submitted to the main queue.
 * 执行提交给主队列的任务blocks
 *
 * @discussion
 * This function "parks" the main thread and waits for blocks to be submitted
 * 
 * to the main queue. This function never returns.
 * 
 * Applications that call NSApplicationMain() or CFRunLoopRun() on the
 * main thread do not need to call dispatch_main().
 *
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NOTHROW DISPATCH_NORETURN
void
dispatch_main(void);
这个函数会阻塞主线程并且等待提交给主队列的任务blocks完成,这个函数永远不会返回.
这个方法会阻塞主线程,然后在其它线程中执行主队列中的任务,这个方法永远不会返回(意思会卡住主线程).

也就是说,把主队列中的任务在其他线程中执行。所以用了dispatch_get_main_queue也不一定是主线程的。
加上这个

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"1");
}

我们会发现打印这句话

Attempting to wake up main runloop, but the main thread as exited. This message will only log once. Break on _CFRunLoopError_MainThreadHasExited to debug.

这就完全验证了dispatch_main()的作用。退出主线程,让其他线程来执行主线程的任务。

当我们把dispatch_main()注释掉之后。上面那段代码的打印

dispatch_main会堵塞主线程
查看是否堵塞主线程
 main thread: 1 //是主线程
 <NSThread: 0x600001596b80>{number = 1, name = main}//是主线程
main queue: 1//是主队列

经过上面几种情况的分析,到底我们需要怎么搞才能确定是保证线程安全的呢?

我查阅了sdwebimage 3.8版本和 4.4.2版本,发现了两种不同的写法

3.8版本

#define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }

#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }

4.4.2版本

#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
        block();\
    } else {\
        dispatch_async(queue, block);\
    }
#endif

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif

那么到底上面两个版本哪个版本才是最安全的呢?
既然sdwebImage最新版本换了方式,那么肯定,4.2.2是最安全的。

3.8版本是我们平时通常使用的版本,那么对于4.2.2又如何解释呢?
iOS UI 操作在主线程不一定安全?
通过这篇文章我了解了不少东西。

第一种方案

      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
        }

第二种方案 ,sdwebImage的方案

//获取主队列名
    const char *main_queue_name = dispatch_queue_get_label(dispatch_get_main_queue());
    const char *other_queue_name = "other_queue_name";
    NSLog(@"\nmain_queue_name====%s", main_queue_name);
    //创建一个和主队列名字一样的串行队列
    dispatch_queue_t customSerialQueue = dispatch_queue_create(other_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 ");
            }
      
            NSLog(@"\nUI Action Finished");
        });
        
    } else {
        //名字不一样
        NSLog(@"cutomSerialQueue is main queue");
                NSLog(@"main thread: %d", [NSThread isMainThread]);
                // 判断是否是主队列
                void *value = dispatch_get_specific(key);//返回与当前分派队列关联的键的值。
                NSLog(@"main queue: %d", value != NULL);
        
    }

总结:我们都知道主队列是串行队列,所以串行队列肯定不会开辟新的线程,也就是说主队列一定会是在主线程执行。
对于更新UI这种操作,要保证在主线程执行,也就是要保证在主队列执行。
1.主线程中的任务一定在主队列中执行吗?
不是。
2.如何保证一定在主线程中执行?
只要保证在主队列中执行就可以了。
3.如何保证既在主线程中执行又在主队列中执行?
保证在主队列中就会及在主线程又在主队列。

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