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


本篇將從四個方面對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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM