iOS開發之多線程技術——GCD篇

来源:http://www.cnblogs.com/Jepson1218/archive/2016/02/03/5180798.html
-Advertisement-
Play Games

本篇將從四個方面對iOS開發中GCD的使用進行詳盡的講解: 一、什麼是GCD 二、我們為什麼要用GCD技術 三、在實際開發中如何使用GCD更好的實現我們的需求 一、Synchronous & Asynchronous 同步 & 非同步 二、Serial Queues & Concurrent Queu


本篇將從四個方面對iOS開發中GCD的使用進行詳盡的講解:


一、什麼是GCD

二、我們為什麼要用GCD技術

三、在實際開發中如何使用GCD更好的實現我們的需求

  一、Synchronous & Asynchronous 同步 & 非同步

  二、Serial Queues & Concurrent Queues 串列 & 併發

  三、Global Queues全局隊列

  四、Main Queue主隊列

  五、同步的作用

  六、dispatch_time延遲操作

  七、線程安全(單例dispatch_once、讀寫dispatch_barrier_async)

  八、調度組(dispatch_group)

四、定時源事件和子線程的運行迴圈


一、什麼是GCD

  GCD 是基於 C 的 API,它是 libdispatch 的市場名稱,而 libdispatch 作為 Apple 的一個庫,為併發代碼在多核硬體(跑 iOS 或 OS X )上執行提供有力支持。

 


二、我們為什麼要用GCD技術

  • GCD 能通過推遲昂貴計算任務併在後臺運行它們來改善你的應用的響應性能。
  • GCD 提供一個易於使用的併發模型而不僅僅只是鎖和線程,以幫助我們避開併發陷阱。
  • GCD 具有在常見模式(例如單例)上用更高性能的原語優化你的代碼的潛在能力。
  • GCD旨在替換NSThread等線程技術
  • GCD可充分利用設備的多核
  • GCD可自動管理線程的生命周期

 


三、在實際開發中如何使用GCD更好的實現我們的需求

一、Synchronous & Asynchronous 同步 & 非同步

1)同步任務執行方式:在當前線程中執行,必須等待當前語句執行完畢,才會執行下一條語句

 

#pragma mark
#pragma mark - 同步方法
/**
 同步的列印順序
 列印 begin
 列印 [NSThread currentThread]
 列印 end
 */
- (void)syncTask {
    
    NSLog(@"begin");
    
    // 1.GCD同步方法
    /**
     參數1:隊列 第一個參數0其實為隊列優先順序DISPATCH_QUEUE_PRIORITY_DEFAULT,如果要適配 iOS 7.0 & 8.0,則始終為0
     參數2:任務
     */
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        // 任務中要執行的代碼
        NSLog(@"%@", [NSThread currentThread]);
        
    });
    
    NSLog(@"end");
    
}
同步方法

 

2)非同步任務執行方式:不在當前線程中執行,不用等待當前語句執行完畢,就可以執行下一條語句

 

#pragma mark
#pragma mark - 非同步方法
/**
 非同步的列印順序
 列印 begin
 列印 一般情況下為end,極少數情況下會很快開闢完新的線程,先列印出[NSThread currentThread]
 */
- (void)asyncTask {
    
    /**
     非同步:不會在“當前線程”執行,會首先去開闢新的子線程,開闢線程需要花費時間
     */
    
    NSLog(@"begin");
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@", [NSThread currentThread]);
    });
    
    NSLog(@"end");
    
}
非同步方法

 

二、Serial Queues & Concurrent Queues 串列 & 併發

1)串列隊列調度同步和非同步任務執行

串列隊列特點:
  以先進先出的方式,順序調度隊列中的任務執行
  無論隊列中所指定的執行任務函數是同步還是非同步,都會等待前一個任務執行完成後,再調度後面的任務

 

#pragma mark
#pragma mark - 串列隊列同步方法
/**
 串列隊列,同步方法
 1.列印順序 : 從上到下,依次列印,因為是串列的
 2.在哪條線程上執行 : 主線程,因為是同步方法,所以在當前線程裡面執行,恰好當前線程是主線程,所以它就在主線程上面執行
 
 應用場景:開發中很少用
 */
- (void)serialSync {
    // 1.創建一個串列隊列
    /**
     參數1:隊列的表示符號,一般是公司的功能變數名稱倒寫
     參數2:隊列的類型
         DISPATCH_QUEUE_SERIAL 串列隊列
         DISPATCH_QUEUE_CONCURRENT 併發隊列
     */
    dispatch_queue_t serialQuene = dispatch_queue_create("com.baidu", DISPATCH_QUEUE_SERIAL);
    
    // 創建任務
    void (^task1) () = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2) () = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3) () = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 添加任務到隊列,同步方法執行
    dispatch_sync(serialQuene, task1);
    dispatch_sync(serialQuene, task2);
    dispatch_sync(serialQuene, task3);
}
串列隊列同步方法

 

#pragma mark
#pragma mark - 串列隊列非同步方法
/**
 串列隊列,非同步方法
 1.列印順序:從上到下,依次執行,它是串列隊列
 2.在哪條線程上執行:在子線程,因為它是非同步執行,非同步就是不在當前線程裡面執行
 
 應用場景:耗時間,有順序的任務
    1.登錄--->2.付費--->3.才能看
 
 */
- (void)serialAsync {
    // 除了第三步,和串列同步方法中都是一樣的
    // 1.創建一個串列隊列
    dispatch_queue_t serialQuene = dispatch_queue_create("com.baidu", DISPATCH_QUEUE_SERIAL);
    
    // 2.創建任務
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 3.添加任務到隊列
    dispatch_async(serialQuene, task1);
    dispatch_async(serialQuene, task2);
    dispatch_async(serialQuene, task3);
}
串列隊列非同步方法

 

2)併發隊列調度非同步任務執行

併發隊列特點:
  以先進先出的方式,併發調度隊列中的任務執行
  如果當前調度的任務是同步執行的,會等待任務執行完成後,再調度後續的任務
  如果當前調度的任務是非同步執行的,同時底層線程池有可用的線程資源,會再新的線程調度後續任務的執行

 

#pragma mark
#pragma mark - 併發隊列同步任務
/**
 併發隊列,同步任務
 1.列印順序:因為是同步,所以依次執行
 2.在哪條線程上執行:主線程,因為它是同步方法,它就在當前線程裡面執行,也就是在主線程裡面依次執行
 
 當併發隊列遇到同步的時候還是依次執行,所以說方法(同步/非同步)的優先順序會比隊列的優先順序高
 
 * 只要是同步方法,都只會在當前線程裡面執行,不會開子線程
 
 應用場景:
    開發中幾乎不用
 
 */
- (void)serialSync {
    
    /**
     參數1:隊列的表示符號,一般是公司的功能變數名稱倒寫
     參數2:隊列的類型
     DISPATCH_QUEUE_SERIAL 串列隊列
     DISPATCH_QUEUE_CONCURRENT 併發隊列
     */
    
    // 1.創建併發隊列
    dispatch_queue_t serialSync = dispatch_queue_create("com.xiaojukeji", DISPATCH_QUEUE_CONCURRENT);
    
    // 2.創建任務
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 3.添加任務到併發隊列
    dispatch_sync(serialSync, task1);
    dispatch_sync(serialSync, task2);
    dispatch_sync(serialSync, task3);
}
併發隊列同步任務
#pragma mark
#pragma mark - 併發隊列非同步任務
/**
 1.列印順序:無序的
 2.在哪條線程上執行:在子線程上執行,每一個任務都在它自己的線程上執行
        可以創建N條子線程,它是由底層可調度線程池來決定的,可調度線程池它是有一個重用機制
 
 應用場景
    同時下載多個影片
 */
- (void)serialAsync {
    // 1.創建併發隊列
    dispatch_queue_t serialAsync = dispatch_queue_create("com.xiaojukeji", DISPATCH_QUEUE_CONCURRENT);
    
    // 2.創建任務
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 3.將任務添加到併發隊列
    dispatch_async(serialAsync, task1);
    dispatch_async(serialAsync, task2);
    dispatch_async(serialAsync, task3);
}
併發隊列非同步任務

 

三、全局隊列

全局隊列是系統為了方便程式員開發提供的,其工作表現與併發隊列一致

全局隊列 & 併發隊列的區別

  全局隊列:沒有名稱,無論 MRC & ARC 都不需要考慮釋放,日常開發中,建議使用"全局隊列"
  併發隊列:有名字,和 NSThread 的 name 屬性作用類似,如果在 MRC 開發時,需要使用 dispatch_release(q); 釋放相應的對象
  dispatch_barrier 必須使用自定義的併發隊列
  開發第三方框架時,建議使用併發隊列

參數
  參數1:服務質量(隊列對任務調度的優先順序)/iOS 7.0 之前,是優先順序

iOS 8.0(新增,暫時不能用,今年年底)
QOS_CLASS_USER_INTERACTIVE 0x21, 用戶交互(希望最快完成-不能用太耗時的操作)
QOS_CLASS_USER_INITIATED 0x19, 用戶期望(希望快,也不能太耗時)
QOS_CLASS_DEFAULT 0x15, 預設(用來底層重置隊列使用的,不是給程式員用的)
QOS_CLASS_UTILITY 0x11, 實用工具(專門用來處理耗時操作!)
QOS_CLASS_BACKGROUND 0x09, 後臺
QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 適配
iOS 7.0
DISPATCH_QUEUE_PRIORITY_HIGH 2 高優先順序
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 預設優先順序
DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優先順序
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 後臺優先順序

  參數2:為未來保留使用的,應該永遠傳入0

  結論:如果要適配 iOS 7.0 & 8.0,使用以下代碼: dispatch_get_global_queue(0, 0);

 

#pragma mark
#pragma mark - 全局隊列同步任務
/**
 全局隊列,同步任務
 1.列印順序:依次執行,因為它是同步的
 2.在哪條線程上執行:主線程,因為它是同步方法,它就在當前線程裡面執行
 
 當它遇到同步的時候,併發隊列還是依次執行,所以說,方法的優先順序比隊列的優先順序高
 
 * 只要是同步方法,都只會在當前線程裡面執行,不會開子線程
 
 應用場景:開發中幾乎不用
 */
- (void)globalSync {
    /**
     參數1:
        IOS7:表示的優先順序
        IOS8:服務質量
        為了保證相容IOS7&IOS8一般傳入0
     
     參數2:未來使用,傳入0
     */
    
    NSLog(@"begin");
    // 1.創建全局隊列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    // 2.創建任務
    void (^task1)() = ^() {
        NSLog(@"task1----%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2----%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3----%@", [NSThread currentThread]);
    };
    
    // 3.添加任務到全局隊列
    dispatch_sync(globalQueue, task1);
    dispatch_sync(globalQueue, task2);
    dispatch_sync(globalQueue, task3);

    NSLog(@"end");
}
全局隊列同步任務
#pragma mark
#pragma mark - 全局隊列非同步任務
/**
 全局隊列,非同步方法
 1.列印順序:無序的
 2.在子線程上執行,每一個任務都在它自己的線程上執行,線程數由底層可調度線程池來決定的,可調度線程池有一個重用機制
 應用場景:
    蜻蜓FM同時下載多個聲音
 */
- (void)globalAsync {
    
    NSLog(@"begin");
    
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    dispatch_async(globalQueue, task1);
    dispatch_async(globalQueue, task2);
    dispatch_async(globalQueue, task3);
    
    NSLog(@"end");
    
}
全局隊列非同步任務

 

四、主隊列

特點
  專門用來在主線程上調度任務的隊列
  不會開啟線程
  以先進先出的方式,在主線程空閑時才會調度隊列中的任務在主線程執行
  如果當前主線程正在有任務執行,那麼無論主隊列中當前被添加了什麼任務,都不會被調度

隊列獲取
  主隊列是負責在主線程調度任務的
  會隨著程式啟動一起創建
  主隊列只需要獲取不用創建

 

#pragma mark
#pragma mark - 主隊列非同步任務
/**
 主隊列,非同步任務
 1.執行順序:依次執行,因為它在主線程裡面執行
 * 似乎與我們的非同步任務有所衝突,但是因為它是主隊列,所以,只在主線程裡面執行
 
 2.是否會開線程:不會,因為它在主線程裡面執行
 
 應用場景:
    當做了耗時操作之後,我們需要回到主線程更新UI的時候,就非它不可
 */
- (void)mainAsync {
    
    NSLog(@"begin");
    
    // 1.創建主隊列
    dispatch_queue_t mainAsync = dispatch_get_main_queue();
    
    // 2.創建任務
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };

    dispatch_async(mainAsync, task1);
    dispatch_async(mainAsync, task2);
    dispatch_async(mainAsync, task3);
    
    NSLog(@"end");
    
}
主隊列非同步任務
#pragma mark
#pragma mark - 主隊列同步方法有問題,不能用是個奇葩,會造成死鎖
/**
 主隊列,同步任務有問題,不能用,彼此都在等對方是否執行完了,所以是互相死等
 主隊列只有在主線程空閑的時候,才會去調度它裡面的任務去執行
 */
- (void)mainSync {
    
    NSLog(@"begin");
    // 1.創建主隊列
    dispatch_queue_t mainSync = dispatch_get_main_queue();
    
    // 2.創建任務
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 3.添加任務到主隊列中
    dispatch_sync(mainSync, task1);
    dispatch_sync(mainSync, task2);
    dispatch_sync(mainSync, task3);
    
    NSLog(@"end");
}
主隊列同步方法有問題,不能用是個奇葩,會造成死鎖

 

Deadlock 死鎖

  兩個(有時更多)東西——在大多數情況下,是線程——所謂的死鎖是指它們都卡住了,並等待對方完成或執行其它操作。第一個不能完成是因為它在等待第二個的完成。但第二個也不能完成,因為它在等待第一個的完成。

 

五、同步的作用

同步任務,可以讓其他非同步執行的任務,依賴某一個同步任務,例如:在用戶登錄之後,才允許非同步下載文件!

#pragma mark
#pragma mark - 模擬登錄下載多個電影數據
/**
 同步的作用:保證我們任務執行的先後順序
 1.登錄
 
 2.同時下載三部電影
 */
- (void)loadManyMovie {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"%@", [NSThread currentThread]);
        // 1.登錄,同步在當前線程裡面工作
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"登錄了---%@", [NSThread currentThread]);
            sleep(3);
            
        });
        
        // 2.同時下載三部電影()
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"正在下載第一個電影---%@", [NSThread currentThread]);
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"正在下載第二個電影---%@", [NSThread currentThread]);
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"正在下載第三個電影---%@", [NSThread currentThread]);
        });
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"電腦將在三秒後關閉---%@", [NSThread currentThread]);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"關機了---%@", [NSThread currentThread]);
            });
        });
        
    });
    
}
模擬登錄下載多個電影數據

 

六、dispatch_time延遲操作

不知道何時適合使用 dispatch_after ?

  • 自定義串列隊列:在一個自定義串列隊列上使用 dispatch_after 要小心。你最好堅持使用主隊列。
  • 主隊列(串列):是使用 dispatch_after 的好選擇;Xcode 提供了一個不錯的自動完成模版。
  • 併發隊列:在併發隊列上使用 dispatch_after 也要小心;你會這樣做就比較罕見。還是在主隊列做這些操作吧。
// MARK: - 延遲執行
- (void)delay {
    /**
     從現在開始,經過多少納秒,由"隊列"調度非同步執行 block 中的代碼

     參數
     1. when    從現在開始,經過多少納秒
     2. queue   隊列
     3. block   非同步執行的任務
     */
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
    void (^task)() = ^ {
        NSLog(@"%@", [NSThread currentThread]);
    };
    // 主隊列
//    dispatch_after(when, dispatch_get_main_queue(), task);
    // 全局隊列
//    dispatch_after(when, dispatch_get_global_queue(0, 0), task);
    // 串列隊列
    dispatch_after(when, dispatch_queue_create("itheima", NULL), task);

    NSLog(@"come here");
}

- (void)after {
    [self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];

    NSLog(@"come here");
}
延遲執行

 

七、線程安全(單例dispatch_once、讀寫dispatch_barrier_async)

  一個常見的擔憂是它們常常不是線程安全的。這個擔憂十分合理,基於它們的用途:單例常常被多個控制器同時訪問。

  單例的線程擔憂範圍從初始化開始,到信息的讀和寫。

  dispatch_once() 以線程安全的方式執行且僅執行其代碼塊一次。試圖訪問臨界區(即傳遞給 dispatch_once 的代碼)的不同的線程會在臨界區已有一個線程的情況下被阻塞,直到臨界區完成為止。

// 使用 dispatch_once 實現單例
+ (instancetype)sharedSingleton {
    static id instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });

    return instance;
}
使用 dispatch_once 實現單例

  線程安全實例不是處理單例時的唯一問題。如果單例屬性表示一個可變對象,那麼你就需要考慮是否那個對象自身線程安全。

  如果問題中的這個對象是一個 Foundation 容器類,那麼答案是——“很可能不安全”!Apple 維護一個有用且有些心寒的列表,眾多的 Foundation 類都不是線程安全的。如:NSMutableArray。

  雖然許多線程可以同時讀取 NSMutableArray 的一個實例而不會產生問題,但當一個線程正在讀取時讓另外一個線程修改數組就是不安全的。在目前的狀況下不能預防這種情況的發生。GCD 通過用 dispatch barriers 創建一個讀者寫者鎖,提供了一個優雅的解決方案。

 

八、調度組(dispatch_group)

#pragma mark
#pragma mark - 調度組
/**
 調度組的實現原理:類似引用計數器進行+1和-1的操作
 應用場景
 比如同時開了三個線程下載視頻,只有當三個視頻完全下載完畢後,我才能做後續的事
 這個就需要用到調度組,這個調度組,就能監聽它裡面的任務是否都執行完畢
 */
- (void)groupDispatch {
    
    // 1.創建調度組
    dispatch_group_t group = dispatch_group_create();
    
    // 2.獲取全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 3.創建三個下載任務
    void (^task1) () = ^(){
        NSLog(@"%@----下載片頭",[NSThread currentThread]);
    };
    
    dispatch_group_enter(group); // 引用計數+1
    void (^task2) () = ^(){
        NSLog(@"%@----下載中間的內容",[NSThread currentThread]);
        
        [NSThread sleepForTimeInterval:3.0];
        
        NSLog(@"--下載中間內容完畢---");
        dispatch_group_leave(group); // 引用計數-1
    };
    
    dispatch_group_enter(group); // 引用計數+1
    void (^task3) () = ^(){
        NSLog(@"%@----下載片尾",[NSThread currentThread]);
        dispatch_group_leave(group); // 引用計數-1
    };
    
    // 4.需要將我們的隊列 和 任務,加入到組內去監控
    dispatch_group_async(group, queue, task1);
    dispatch_group_async(group, queue, task2);
    dispatch_group_async(group, queue, task3);
    
    // 5.監聽的函數
    /**
     遠離:來監聽當調度組的引用計數器為0時,才會執行該函數中內容,否則不會執行
     參數1:組
     參數2:決定了參數3在哪個線程裡面執行
     參數3:組內完全下載完畢後需要執行的代碼
     */
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 表示組內的所有內容全部下載完成後會來到這裡
        NSLog(@"把下好的視頻按照順序拼接好,然後顯示在UI去播放%@", [NSThread currentThread]);
    });
    
}
調度組

 

1.因為你在使用的是同步的 dispatch_group_wait ,它會阻塞當前線程,所以你要用 dispatch_async 將整個方法放入後臺隊列以避免阻塞主線程。

2.創建一個新的 Dispatch Group,它的作用就像一個用於未完成任務的計數器。
3.dispatch_group_enter 手動通知 Dispatch Group 任務已經開始。你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對出現,否則你可能會遇到詭異的崩潰問題。
4.手動通知 Group 它的工作已經完成。再次說明,你必須要確保進入 Group 的次數和離開 Group 的次數相等。
5.dispatch_group_wait 會一直等待,直到任務全部完成或者超時。如果在所有任務完成前超時了,該函數會返回一個非零值。你可以對此返回值做條件判斷以確定是否超出等待周期;然而,你在這裡用 DISPATCH_TIME_FOREVER 讓它永遠等待。它的意思,勿庸置疑就是,永-遠-等-待!這樣很好,因為圖片的創建工作總是會完成的。
6.此時此刻,你已經確保了,要麼所有的圖片任務都已完成,要麼發生了超時。然後,你在主線程上運行 completionBlock 回調。這會將工作放到主線程上,併在稍後執行。
7.最後,檢查 completionBlock 是否為 nil,如果不是,那就運行它。
編譯並運行你的應用,嘗試下載多個圖片,觀察你的應用是在何時運行 completionBlock 的。

註意:如果你是在真機上運行應用,而且網路活動發生得太快以致難以觀察 completionBlock 被調用的時刻,那麼你可以在 Settings 應用里的開發者相關部分里打開一些網路設置,以確保代碼按照我們所期望的那樣工作。只需去往 Network Link Conditioner 區,開啟它,再選擇一個 Profile,“Very Bad Network” 就不錯。
如果你是在模擬器里運行應用,你可以使用 來自 GitHub 的 Network Link Conditioner 來改變網路速度。它會成為你工具箱中的一個好工具,因為它強制你研究你的應用在連接速度並非最佳的情況下會變成什麼樣。

 


四、定時源事件和子線程的運行迴圈

 1 - (void)viewDidLoad {
 2     [super viewDidLoad];
 3     
 4     NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];
 5     
 6     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
 7     
 8 }
 9 
10 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
11     [self performSelectorInBackground:@selector(subThreadRun) withObject:nil];
12 }
13 
14 #pragma mark
15 #pragma mark - 子線程的運行迴圈
16 - (void)subThreadRun {
17     
18     NSLog(@"%@----%s", [NSThread currentThread], __func__);
19     
20     // 1.定義一個定時器
21     NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];
22     
23     // 2.將我們的定時器加入到運行迴圈,只有加入到當前的運行迴圈裡面去,他才知道你這個時候,有一個定時任務
24     /**
25      NSDefaultRunLoopMode 當拖動的時候,它會停掉
26      因為這種模式是互斥的
27      forMode:UITrackingRunLoopMode 只有輸入的時候,它才會去執行定時器任務
28      
29      NSRunLoopCommonModes 包含了前面兩種
30      
31     //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
32     //[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
33      */
34     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
35     
36     // 下載、定時源時間、輸入源時間,如果放在子線程裡面,如果想要它執行任務,就必須開啟子線程的運行迴圈
37     CFRunLoopRun();
38     
39 }
40 
41 - (void)timeEvent {
42     
43     NSLog(@"%d----%@", self.count, [NSThread currentThread]);
44     
45     if (self.count++ == 10) {
46         NSLog(@"---掛了----");
47         // 停止當前的運行迴圈
48         CFRunLoopStop(CFRunLoopGetCurrent());
49     }
50     
51 }

  

溫馨提示:在完成本篇Blog的過程中,http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1給了我很大的提示,感謝Derek Selander

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • checbox覆選框實現radio單選框的單選功能:大家知道覆選框可以一次選中多個,單選按鈕每次只能夠選中其中的一個,但是單選按鈕比較霸道,你選中以後,只能夠且必須選中其中一個,所有下麵就通過checkbox覆選框模擬實現單選按鈕的功能,但是能夠取消選中的項。代碼如下: <!DOCTYPE html
  • 原生js實現的創建和刪除元素實例代碼:在實際應用中,往往需要動態的創建和刪除指定的元素,下麵就通過代碼實例介紹一下如何實現此功能。代碼實例如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author" con
  • 絕對定位對margin外邊距的影響:關於什麼是絕對定位和外邊距這裡就不多介紹了,具體可以參閱以下兩篇文章。(1).絕對定位可以參閱CSS的絕對定位一章節。(2).外邊距可以參閱CSS的外邊距一章節。下麵就通過代碼實例介紹一下絕對定位對於margin外邊距的影響。代碼實例如下: <!DOCTYPE h
  • $.extend()和$.fn.extend()函數用法簡單介紹:標題中的兩個方法在jQuery插件開發中非常的重要,下麵通過簡單的代碼實例介紹一下它們的用法。本章節不會介紹它的原理,而是只給出它們的作用是什麼,能夠做什麼事情。jQuery.extend()可以為jQuery類添加新的方法,類似於c
  • javascript如何設置指定標簽的透明度:在實際應用中,可能需要動態的設置標簽的透明度,現在就以div為例子介紹一下如何實現此效果。代碼實例如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author"
  • body在預設情況下是具有margin外邊距的:這裡只是陳述一個事實,那就是body具有外邊距在預設情況下。代碼實例如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="author" content="http:/
  • 本篇將從四個方面對iOS開發中使用到的NSOperation技術進行講解: 一、什麼是NSOperation 二、我們為什麼使用NSOperation 三、在實際開發中如何使用NSOperation 1、自定義NSOperation 2、NSOperation的基本使用 3、NSOperation實
  • 分類:C#、Android; 日期:2016-02-04 3.5 示例5--多地圖展示 一、簡介 地圖控制項自v2.3.5版本起,支持多實例,即開發者可以在一個頁面中建立多個地圖對象,並且針對這些對象分別操作且不會產生相互干擾。 文件名:Demo04MultiMapView.cs 簡介:介紹多MapV
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...