玩轉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


最後

碼字很費腦, 看官賞點飯錢可好

微信

支付寶
相關文章
相關標籤/搜索