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
  • 概述:本文代碼示例演示瞭如何在WPF中使用LiveCharts庫創建動態條形圖。通過創建數據模型、ViewModel和在XAML中使用`CartesianChart`控制項,你可以輕鬆實現圖表的數據綁定和動態更新。我將通過清晰的步驟指南包括詳細的中文註釋,幫助你快速理解並應用這一功能。 先上效果: 在 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • 概述:本示例演示了在WPF應用程式中實現多語言支持的詳細步驟。通過資源字典和數據綁定,以及使用語言管理器類,應用程式能夠在運行時動態切換語言。這種方法使得多語言支持更加靈活,便於維護,同時提供清晰的代碼結構。 在WPF中實現多語言的一種常見方法是使用資源字典和數據綁定。以下是一個詳細的步驟和示例源代 ...
  • 描述(做一個簡單的記錄): 事件(event)的本質是一個委托;(聲明一個事件: public event TestDelegate eventTest;) 委托(delegate)可以理解為一個符合某種簽名的方法類型;比如:TestDelegate委托的返回數據類型為string,參數為 int和 ...
  • 1、AOT適合場景 Aot適合工具類型的項目使用,優點禁止反編 ,第一次啟動快,業務型項目或者反射多的項目不適合用AOT AOT更新記錄: 實實在在經過實踐的AOT ORM 5.1.4.117 +支持AOT 5.1.4.123 +支持CodeFirst和非同步方法 5.1.4.129-preview1 ...
  • 總說周知,UWP 是運行在沙盒裡面的,所有許可權都有嚴格限制,和沙盒外交互也需要特殊的通道,所以從根本杜絕了 UWP 毒瘤的存在。但是實際上 UWP 只是一個應用模型,本身是沒有什麼許可權管理的,許可權管理全靠 App Container 沙盒控制,如果我們脫離了這個沙盒,UWP 就會放飛自我了。那麼有沒... ...
  • 目錄條款17:讓介面容易被正確使用,不易被誤用(Make interfaces easy to use correctly and hard to use incorrectly)限制類型和值規定能做和不能做的事提供行為一致的介面條款19:設計class猶如設計type(Treat class de ...
  • title: 從零開始:Django項目的創建與配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 後端開發 tags: Django WebDev Python ORM Security Deployment Op ...
  • 1、BOM對象 BOM:Broswer object model,即瀏覽器提供我們開發者在javascript用於操作瀏覽器的對象。 1.1、window對象 視窗方法 // BOM Browser object model 瀏覽器對象模型 // js中最大的一個對象.整個瀏覽器視窗出現的所有東西都 ...