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

本文探讨了在iOS开发中主线程与主队列之间的区别,并通过具体案例分析了一个与MapKit框架有关的bug。文章指出,在某些情况下,即使在主线程中执行UI操作也可能出现问题,特别是当涉及到特定API时。为了解决这个问题,提出了一个更为严谨的检查方法,以确保代码运行在主队列中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接:http://www.jianshu.com/p/d15f4b37b0f2


从最初开始学习 iOS 的时候,我们就被告知 UI 操作一定要放在主线程进行。这是因为 UIKit 的方法不是线程安全的,保证线程安全需要极大的开销。


那么问题来了,在主线程中进行 UI 操作一定是安全的么?


在苹果的 MapKit 框架中,有一个叫做 addOverlay 的方法,它在底层实现的时候,不仅仅要求代码执行在主线程上,还要求执行在 GCD 的主队列上。这是一个极罕见的问题,但已经有人在使用 ReactiveCocoa 时踩到了坑,并提交了 issue苹果的 Developer Technology Support 承认这是一个 bug。


为了避免再次掉进同样的坑,我认为有必要分析一下问题发生的原因和解决方案。


GCD 知识复习


在 GCD 中,使用 dispatch_get_main_queue() 函数可以获取主队列。调用 dispatch_sync() 方法会把任务同步提交到指定的队列。


当我们同步提交一个任务时,首先会阻塞当前队列,然后等到下一次 runloop 时再在合适的线程中执行 block。在执行 block 之前,首先会寻找合适的线程来执行block,然后阻塞这个线程,直到 block 执行完毕。


寻找线程的规则是: 任何提交到主队列的 block 都会在主线程中执行,在不违背此规则的前提下,文档还告诉我们系统会自动进行优化,尽可能的在当前线程执行 block


原因分析


回到之前描述的 bug 中,即使是在主线程中执行的代码,也很可能不是运行在主队列中(反之则必然)。


如果我们在子队列中调用  MapKit 的 addOverlay 方法,即使当前处于主线程,也会导致 bug 的产生,因为这个方法的底层实现判断的是主队列而非主线程。


更进一步的思考,有时候为了保证 UI 操作在主线程运行,如果有一个函数可以用来创建新的 UILabel,为了确保线程安全,代码可能是这样:


- (UILabel *)labelWithText: (NSString *)text {

__block UILabel *theLabel;

if ([NSThread isMainThread]) {

theLabel = [[UILabel alloc] init];

[theLabel setText:text];

}

else {

dispatch_sync(dispatch_get_main_queue(), ^{

theLabel = [[UILabel alloc] init];

[theLabel setText:text];

});

}

return theLabel;

}


从严格意义上来讲,这样的写法不是 100% 安全的,因为我们无法得知相关的系统方法是否存在上述 Bug。


解决方案


用 dispatch_queue_set_specific 和 dispatch_get_specific 这一组方法为主队列打上标记。由于提交到主队列的 block 一定在主线程运行,并且在 GCD 中线程切换通常都是由指定某个队列引起的,我们可以做一个更加严格的判断,即用判断是否处于主队列来代替是否处于主线程:


+ (BOOL)isMainQueue {

static const void* mainQueueKey = @"mainQueue";

static void* mainQueueContext = @"mainQueue";

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueContext, nil);

});

return dispatch_get_specific(mainQueueKey) == mainQueueContext;

}


用 isMainQueue 方法代替 [NSThread isMainThread] 即可获得更好的安全性。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值