问题
最近在看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
由此,我们可以大胆的猜测,苹果的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_specific 和 dispatch_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 源码中的一段代码而引发出的对主队列,主线程的一些思考;如有不正确,还请大神指导,轻喷~~
网友评论