iOS 主线程和主队列

先说结论, 主队列的任务只在主线程中被执行的,而主线程运行的是一个 runloop,不仅仅只有主队列的中的任务,还会处理 UI 的布局和绘制任务, 同时还可能会有其他队列中的任务。

举个例子

问题1: 主线程是否只做主队列的事情?

- (void)someMethod {
    dispatch_queue_t queue = dispatch_queue_create("com.kk", nil);
    dispatch_sync(queue, ^{
        NSLog(@"current thread = %@, curren queue = %@, main queue = %@",
              [NSThread currentThread],
              [NSString stringWithCString:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) encoding:NSUTF8StringEncoding],
              [NSString stringWithCString:dispatch_queue_get_label(dispatch_get_main_queue()) encoding:NSUTF8StringEncoding]);
         });
}

------------------

current thread = <NSThread: 0x600002c0cf00>{number = 1, name = main}, 
curren queue = com.kk, 
main queue = com.apple.main-thread

结论: 主线程不止做主队列的任务, 也会做了其他队列的任务 。

主线程还有可能会执行其他队列的任务。这是为了避免线程切换对性能的消耗。因为CPU的调度多线程势必会不断切换上下文。这样每个线程需要一个上下文来记录当前执行状态。这样新线程被执行时首先将上下文写入寄存器,执行结束寄存器重新写入上下文,如此不断切换才避免了多线程的数据混乱。

问题2: 主队列任务是否一定在主线程执行?

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // dispatch_sync和用dispatch_async , 一样都会回到主线程
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"%@",[NSThread currentThread]);
            NSLog(@"[NSThread isMainThread] - %d",[NSThread isMainThread]);
        });
    });

------------------

//  <_NSMainThread: 0x6000008980c0>{number = 1, name = main}
// [NSThread isMainThread] - 1

结论:所有主队列任务只会在主线程中执行,下面分类讨论:

  • 如果在子线程调用dispatch_sync(dispatch_get_main_queue() 或者 dispatch_async(dispatch_get_main_queue() , 会回到主线程, 保证主队列的任务在主线程中执行
  • 如果在主线程使用dispatch_sync(dispatch_get_main_queue() 会死锁, block内的内容不会执行, 程序卡死
  • 如果在主线程使用dispatch_async(dispatch_get_main_queue() 在下一个主线程的runloop中执行
  • 所以结合所有情况,所有主队列的任务只会在主线程中执行。

主线程判断的几个方法

下面这几种切换到主线程执行的方法,有什么优缺点?

  • 方法1
    if ([NSThread isMainThread]) {
        //xxx
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            //xxx
        });
    }

使用sync/async回到主线程,大部分都是这么用的,也没有什么问题, 下面会说到一种特殊情况
  • 方法2
dispatch_async(dispatch_get_main_queue(), ^{
//xxx
});

始终异步放在下一个loop使用,会延迟执行时机, 等到下一个runloop才会执行
  • 方法3
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {
        //xxx
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            //xxx
        });
    }

通过判断当前队列label的形式,可以保证任务一定是放在主队列中的。
上面已经说过, 主队列的任务一定是在主线程执行的.

综合来说,方法3是更好的判断是否是主线程的方式。

为什么推荐方法3

队列不是线程,从Apple的GCD guide中了解到,不能保证调度队列将在单独的线程上执行。 GCD将确定哪个线程,并在必要时创建一个新线程。 

查阅了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是最安全的。

那SDWebImage为什么要升级呢?

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

由此,我们得想一个更加安全的方式规避这种即使有此类系统bug,万一别的API也有这样的问题也不至于导致APP出问题.

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

所以更推荐使用方案3来作为是不是主线程的判断.


既然知道了原理, 那就写代码验证下.

假设下面-addRedView是系统的某个方法,这个方法要求必须在主线程 && 必须是主队列

然后使用方案1和方案3调用, 查看会不会有问题.

// 假设这个方法是系统的某个方法, 要求必须在主线程 && 必须是主队列, 否则触发crash
- (void)addRedView {

    if ([NSThread isMainThread]) {

    } else {
        NSLog(@"不是主线程");
        NSAssert(NO, @"不是主线程");
    }
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {

    } else {
        NSLog(@"不是主队列");
        NSAssert(NO, @"不是主队列");
    }
    UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 40)];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("com.kk", nil);
    dispatch_sync(queue, ^{
        // 当前代码,在主线程,而不在主队列

        if ([NSThread isMainThread]) {
            [self addRedView]; // 执行这行, 会崩溃
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self addRedView];
            });
        }

        if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {
            [self addRedView];
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self addRedView]; // 执行这行, 不会崩溃
            });
        }

    });

}

经过测试, 使用方案3确实可以避免崩溃, 这也是SD等三方框架升级写法的原因.

参考文章: 

主队列&主线程

在主线程执行_主线程和主队列的关系

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

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

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值