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