玩转iOS开发:实战开发中的GCD Tips小技巧 (一)

文章分享至个人我的技术博客: https://cainluo.github.io/15061581251764.htmlhtml


这篇文章主要是给以前的GCD文章做为一个补充, 顺便讲讲一些在实际开发中遇到的问题和一些解决的办法, 若是没有看过以前文章的朋友能够去看看:git

转载声明:如须要转载该文章, 请联系做者, 而且注明出处, 以及不能擅自修改本文.github


线程死锁与解决办法

不恰当的使用线程形成的死锁

在咱们实际开发中, 何时会遇到线程死锁呢? 估计有不少小伙伴就能够想到了, 就是在主队列里同步执行不一样线程的时候:vim

- (void)mineQueueLockThread {
    
    dispatch_queue_t queue = dispatch_queue_create("mineQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        
        NSLog(@"第一次执行, %@", [NSThread currentThread]);
        
        dispatch_sync(queue, ^{
            
            NSLog(@"第二次执行, %@", [NSThread currentThread]);
        });
    });
}
复制代码

打印结果:bash

2017-09-23 17:40:05.147 GCD-Tips[56843:3169265] 开始执行
2017-09-23 17:40:05.147 GCD-Tips[56843:3169265] 第一次执行, <NSThread: 0x60000007c8c0>{number = 1, name = main}
(lldb) 
复制代码

1

看到上面的图, 咱们就能够看到任务执行到这里就死掉了, 也就是咱们所说的卡线程, 那咱们改改吧, 有的朋友会想到, 我把里面的那个任务改为异步不就行了么? 那咱们来看看:微信

- (void)mineQueueLockThread {
    
    dispatch_queue_t queue = dispatch_queue_create("mineQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        
        NSLog(@"第一次执行, %@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            
            NSLog(@"第二次执行, %@", [NSThread currentThread]);
        });
    });
}
复制代码

打印结果:app

2017-09-23 17:44:12.488 GCD-Tips[56903:3176227] 开始执行
2017-09-23 17:44:12.488 GCD-Tips[56903:3176227] 第一次执行, <NSThread: 0x60000006ac40>{number = 1, name = main}
2017-09-23 17:44:12.489 GCD-Tips[56903:3176345] 第二次执行, <NSThread: 0x600000075ec0>{number = 3, name = (null)}
复制代码

事实告诉咱们这是正常的, 那还有另外一种呢? 直接改外部的任务为异步执行怎么样?异步

- (void)mineQueueLockThread {
    
    dispatch_queue_t queue = dispatch_queue_create("mineQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        
        NSLog(@"第一次执行, %@", [NSThread currentThread]);
        
        dispatch_sync(queue, ^{
            
            NSLog(@"第二次执行, %@", [NSThread currentThread]);
        });
    });
}
复制代码
2017-09-23 17:45:52.740 GCD-Tips[56936:3178609] 开始执行
2017-09-23 17:45:52.741 GCD-Tips[56936:3178739] 第一次执行, <NSThread: 0x600000070640>{number = 3, name = (null)}
(lldb) 
复制代码

2

看到结果, 挂了, 为何呢? 按道理来讲, 外部是异步, 而里面是同步是不会卡死的, 其实在以前的文章里咱们就提到过.async

首先, 咱们知道第一个是异步执行的, 这是没有问题的, 问题就是在第二个任务, 咱们都知道这里是在主线程中执行, 可是别忘了, 主线程正在执行着任务, 因此这时候咱们再去执行, 那问题就出现了, 因此就卡死了啦~ui

解决的办法, 有两个:

  • 1.要么把第二个任务变成异步执行.
  • 2.要么把两个任务都变成异步执行.

PS: 这里不要在同步执行嵌套串行队列, 哪怕你是分开小方法里也是同样的.

使用dispatch_apply形成的死锁

当咱们不恰当的时候dispatch_apply的时候也会形成死锁的状况:

- (void)applyLockThread {
    
    dispatch_queue_t queue = dispatch_queue_create("applyQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_apply(3, queue, ^(size_t i) {
        
        NSLog(@"延迟添加一");
        
        dispatch_apply(3, queue, ^(size_t j) {
            
            NSLog(@"延迟添加二");
        });
    });
}
复制代码

打印的结果:

2017-09-24 00:13:10.760 GCD-Tips[57745:3330022] 开始执行
2017-09-24 00:13:10.761 GCD-Tips[57745:3330022] 延迟添加一
(lldb)
复制代码

3

这里咱们是串行队列, 因此大体道理和上面的差很少, 那怎么解决呢? 咱们能够把串行队列, 改为并行队列:

- (void)applyLockThread {
    
    dispatch_queue_t queue = dispatch_queue_create("applyQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_apply(3, queue, ^(size_t i) {
        
        NSLog(@"延迟添加一");
        
        dispatch_apply(3, queue, ^(size_t j) {
            
            NSLog(@"延迟添加二");
        });
    });
}
复制代码
2017-09-24 00:13:58.238 GCD-Tips[57777:3331061] 开始执行
2017-09-24 00:13:58.239 GCD-Tips[57777:3331061] 延迟添加一
2017-09-24 00:13:58.239 GCD-Tips[57777:3331183] 延迟添加一
2017-09-24 00:13:58.239 GCD-Tips[57777:3331061] 延迟添加二
2017-09-24 00:13:58.239 GCD-Tips[57777:3331198] 延迟添加一
2017-09-24 00:13:58.239 GCD-Tips[57777:3331061] 延迟添加二
2017-09-24 00:13:58.239 GCD-Tips[57777:3331183] 延迟添加二
2017-09-24 00:13:58.240 GCD-Tips[57777:3331061] 延迟添加二
2017-09-24 00:13:58.240 GCD-Tips[57777:3331198] 延迟添加二
2017-09-24 00:13:58.240 GCD-Tips[57777:3331183] 延迟添加二
2017-09-24 00:13:58.240 GCD-Tips[57777:3331198] 延迟添加二
2017-09-24 00:13:58.240 GCD-Tips[57777:3331183] 延迟添加二
2017-09-24 00:13:58.240 GCD-Tips[57777:3331198] 延迟添加二
复制代码

队列组的等待

以前咱们在文章里有提过使用dispatch_apply, 这是一个延迟提交任务到线程中执行的方法, 除了这个任务延迟提交以外, 在队列组当中也有一个对应的方法, 叫作dispatch_after.

- (void)groupQueueAfter {
    
    dispatch_time_t timeOne = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    dispatch_time_t timeTwo = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    dispatch_after(timeOne, mainQueue, ^{
        
        NSLog(@"延迟添加一");
    });
    
    dispatch_after(timeTwo, mainQueue, ^{

        NSLog(@"延迟添加二");
    });
}
复制代码

打印的结果:

2017-09-24 00:28:35.205 GCD-Tips[57882:3349738] 开始执行
2017-09-24 00:28:37.405 GCD-Tips[57882:3349738] 延迟添加一
2017-09-24 00:28:38.504 GCD-Tips[57882:3349738] 延迟添加二
复制代码

从打印中, 咱们能够看得出的确是延迟添加了, 但这里须要注意一点, dispatch_after只是延迟提交Block而已, 并非延后当即执行, 不能作到精准控制的.

这里解释一下dispatch_time的第二个参数:

#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
复制代码
  • NSEC_PER_SEC,每秒有多少纳秒。
  • USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
  • NSEC_PER_USEC,每毫秒有多少纳秒。

若是咱们要延迟1秒的话, 能够有几种写法:

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);

dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);

dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);
复制代码

最后一个USEC_PER_SEC * NSEC_PER_USEC,翻译过来就是每秒的毫秒数乘以每毫秒的纳秒数,也就是每秒的纳秒数,因此,延时500毫秒之类的,也就不难了吧~

dispatch_after的嵌套

在这里, dispatch_after是能够嵌套使用的, 但须要注意的是, 虽然并不会形成锁线程, 但会致使延迟提交Block提早:

- (void)groupQueueAfterNested {
    
    dispatch_time_t timeOne = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    dispatch_time_t timeTwo = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_after(timeOne, mainQueue, ^{
        
        NSLog(@"延迟添加一");
        
        
        dispatch_after(timeTwo, mainQueue, ^{
            
            NSLog(@"延迟添加二");
        });
    });
}
复制代码
2017-09-24 00:31:16.810 GCD-Tips[57923:3354999] 开始执行
2017-09-24 00:31:19.010 GCD-Tips[57923:3354999] 延迟添加一
2017-09-24 00:31:19.885 GCD-Tips[57923:3354999] 延迟添加二
复制代码

因此咱们在这里使用dispatch_after的时候, 要注意延迟添加的时机了.


dispatch_apply的注意

在以前的文章里, 咱们有提过dispatch_apply, 这里补充一点就是它在使用的时候, 不管是串行仍是并行队列都同样, 它将会阻塞主线程, 咱们来看代码:

- (void)queueApply {
    
    NSLog(@"开始执行, 当前线程为:%@", [NSThread currentThread]);
    
    dispatch_queue_t queueOne = dispatch_queue_create("applyQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queueTwo = dispatch_queue_create("applyQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_apply(3, queueOne, ^(size_t i) {
        
        NSLog(@"延迟执行一: %zu, 当前线程为:%@", i, [NSThread currentThread]);
    });
    
    dispatch_apply(3, queueTwo, ^(size_t i) {
        
        NSLog(@"延迟执行二: %zu, 当前线程为:%@", i, [NSThread currentThread]);
    });
    
    NSLog(@"结束执行, 当前线程为:%@", [NSThread currentThread]);
}
复制代码
2017-09-24 00:44:04.704 GCD-Tips[58065:3373668] 开始执行
2017-09-24 00:44:04.705 GCD-Tips[58065:3373668] 开始执行, 当前线程为:<NSThread: 0x600000067880>{number = 1, name = main}
2017-09-24 00:44:04.705 GCD-Tips[58065:3373668] 延迟执行一: 0, 当前线程为:<NSThread: 0x600000067880>{number = 1, name = main}
2017-09-24 00:44:04.706 GCD-Tips[58065:3373668] 延迟执行一: 1, 当前线程为:<NSThread: 0x600000067880>{number = 1, name = main}
2017-09-24 00:44:04.706 GCD-Tips[58065:3373668] 延迟执行一: 2, 当前线程为:<NSThread: 0x600000067880>{number = 1, name = main}
2017-09-24 00:44:04.706 GCD-Tips[58065:3373668] 延迟执行二: 0, 当前线程为:<NSThread: 0x600000067880>{number = 1, name = main}
2017-09-24 00:44:04.706 GCD-Tips[58065:3373784] 延迟执行二: 1, 当前线程为:<NSThread: 0x60800006f440>{number = 3, name = (null)}
2017-09-24 00:44:04.706 GCD-Tips[58065:3373786] 延迟执行二: 2, 当前线程为:<NSThread: 0x60800006f380>{number = 4, name = (null)}
2017-09-24 00:44:04.707 GCD-Tips[58065:3373668] 结束执行, 当前线程为:<NSThread: 0x600000067880>{number = 1, name = main}
复制代码

总结

好了, 此次就讲到这里, 一会儿讲太多也很差消化, 剩下的留着以后再讲吧.


工程地址

项目地址: https://github.com/CainRun/iOS-Project-Example/tree/master/GCD-Tips/GCD-Tips-One


最后

码字很费脑, 看官赏点饭钱可好

微信

支付宝
相关文章
相关标签/搜索