歡迎閱讀iOS探索系列(按序閱讀食用效果更加)程序員
GCD全稱是Grand Central Dispatch
,它是純 C 語言,而且提供了很是多強大的函數面試
GCD的優點:安全
咱們要關注的點就是:GCD的核心——將任務添加到隊列,而且指定執行任務的函數bash
dispatch_block_t block = ^{
NSLog(@"GCD的基本使用");
};
dispatch_queue_t queue = dispatch_queue_create("com.Felix", NULL);
dispatch_async(queue, block);
複製代碼
這串代碼最能體現GCD的核心:網絡
dispatch_block_t
使用block封裝任務dispatch_queue_t
建立隊列dispatch_async
將任務添加到隊列上述代碼一般也寫成這種形式多線程
dispatch_queue_t queue = dispatch_queue_create("com.Felix", NULL);
dispatch_async(queue, ^{
NSLog(@"GCD的基本使用");
});
複製代碼
多線程執行任務分爲dispatch_sync
同步執行任務和dispatch_async
異步執行:併發
dispatch_sync
同步執行
dispatch_async
異步執行
串行隊列
(
Serial Dispatch Queue)和
併發隊列(
Concurrent Dispatch Queue
):
串行隊列
:線程執行只能依次逐一前後有序的執行,等待上一個執行完再執行下一個
dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL)
建立串行隊列dispatch_queue_create("xxx", NULL)
建立串行隊列(GCD底層會講到)主隊列
:綁定主線程,全部任務都在主線程中執行、通過特殊處理的串行的隊列
dispatch_get_main_queue()
獲取主隊列併發隊列
:線程能夠同時一塊兒執行,不須要等待上一個執行完就能執行下一個任務
dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);
建立併發隊列全局隊列
:系統提供的併發隊列
dispatch_get_global_queue(0, 0)
獲取系統提供的併發隊列DISPATCH_QUEUE_PRIORITY_DEFAULT
=0DISPATCH_QUEUE_PRIORITY_HIGH
、DISPATCH_QUEUE_PRIORITY_DEFAULT
、DISPATCH_QUEUE_PRIORITY_LOW
、DISPATCH_QUEUE_PRIORITY_BACKGROUND
主隊列和全局隊列單獨考慮,組合結果以總結表格爲準 app
任務一個接一個執行,不開闢線程異步
- (void)test {
NSLog(@"主線程-%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"串行&同步線程%d-%@", i, [NSThread currentThread]);
});
}
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步線程0-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步線程1-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼
任務一個接一個執行,會開闢線程socket
- (void)test {
NSLog(@"主線程-%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"串行&異步線程%d-%@", i, [NSThread currentThread]);
});
}
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&異步線程0-<NSThread: 0x6000009b8880>{number = 6, name = (null)}
// 串行&異步線程1-<NSThread: 0x6000009b8880>{number = 6, name = (null)}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼
任務一個接一個執行,不開闢線程
- (void)test {
NSLog(@"主線程-%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"併發&同步線程%d-%@", i, [NSThread currentThread]);
});
}
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步線程0-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步線程1-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼
任務亂序執行,開闢線程
- (void)test {
NSLog(@"主線程-%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"併發&異步線程%d-%@", i, [NSThread currentThread]);
});
}
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600002a9cd40>{number = 1, name = main}
// 併發&異步線程1-<NSThread: 0x600002a9ca40>{number = 5, name = (null)}
// 併發&異步線程0-<NSThread: 0x600002add3c0>{number = 4, name = (null)}
// ...亂序輸出
--------------------輸出結果:-------------------
複製代碼
下面來看一下主隊列
和全局隊列
的使用狀況:
相互等待,形成死鎖
- (void)test {
NSLog(@"主線程-%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"主隊列&同步線程%d-%@", i, [NSThread currentThread]);
});
}
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 崩潰...
--------------------輸出結果:-------------------
複製代碼
任務一個接一個執行,不開闢線程
- (void)test {
NSLog(@"主線程-%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"主隊列&異步線程%d-%@", i, [NSThread currentThread]);
});
}
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 主隊列&異步線程0-<NSThread: 0x600001980d40>{number = 1, name = main}
// 主隊列&異步線程1-<NSThread: 0x600001980d40>{number = 1, name = main}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼
任務一個接一個執行,不開闢線程(同併發+同步)
- (void)test {
NSLog(@"主線程-%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"全局隊列&同步線程%d-%@", i, [NSThread currentThread]);
});
}
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 全局隊列&同步線程0-<NSThread: 0x60000099c080>{number = 1, name = main}
// 全局隊列&同步線程1-<NSThread: 0x60000099c080>{number = 1, name = main}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼
任務亂序執行,開闢線程(同併發+異步)
- (void)test {
NSLog(@"主線程-%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"全局隊列&異步線程%d-%@", i, [NSThread currentThread]);
});
}
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600001cd4ec0>{number = 1, name = main}
// 全局隊列&異步線程2-<NSThread: 0x600001c8eb00>{number = 3, name = (null)}
// 全局隊列&異步線程3-<NSThread: 0x600001c82b80>{number = 7, name = (null)}
// ...亂序輸出
--------------------輸出結果:-------------------
複製代碼
總結一下:
執行\隊列 | 串行隊列 | 併發隊列 | 主隊列 | 全局隊列 |
---|---|---|---|---|
同步執行 | 按序執行,不開闢線程 | 按序執行,不開闢線程 | 死鎖 | 按序執行,不開闢線程 |
異步執行 | 按序執行,開闢線程 | 亂序執行,開闢線程 | 按序執行,不開闢線程 | 亂序執行,開闢線程 |
dispatch_after
表示在某隊列中的block延遲執行
應用場景:在主隊列上延遲執行一項任務,如viewDidload以後延遲1s,提示一個alertview(是延遲加入到隊列,而不是延遲執行)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2秒後輸出");
});
複製代碼
dispatch_once
保證在App運行期間,block中的代碼只執行一次
應用場景:單例
、method-Swizzling
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//建立單例、method swizzled或其餘任務
});
複製代碼
dispatch_apply
將指定的Block追加到指定的隊列中重複執行,並等到所有的處理執行結束——至關於線程安全的for循環
應用場景:用來拉取網絡數據後提早算出各個控件的大小,防止繪製時計算,提升表單滑動流暢性
- (void)test {
/** param1:重複次數 param2:追加的隊列 param3:執行任務 */
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
NSLog(@"dispatch_apply前");
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"dispatch_apply的線程%zu-%@", index, [NSThread currentThread]);
});
NSLog(@"dispatch_apply後");
}
--------------------輸出結果:-------------------
// dispatch_apply前
// dispatch_apply的線程0-<NSThread: 0x6000019f8d40>{number = 1, name = main}
// ...是否按序輸出與串行隊列仍是併發隊列有關
// dispatch_apply後
--------------------輸出結果:-------------------
複製代碼
dispatch_group_t
:調度組將任務分組執行,能監放任務組完成,並設置等待時間
應用場景:多個接口請求以後刷新頁面
dispatch_group_notify
在dispatch_group_async
執行結束以後會受到通知
- (void)test {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"請求一完成");
});
dispatch_group_async(group, queue, ^{
NSLog(@"請求二完成");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新頁面");
});
}
--------------------輸出結果:-------------------
// 請求二完成
// 請求一完成
// 刷新頁面
--------------------輸出結果:-------------------
複製代碼
dispatch_group_enter
和dispatch_group_leave
成對出現,使進出組的邏輯更加清晰
- (void)test {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"請求一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"請求二完成");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新頁面");
});
}
--------------------輸出結果:-------------------
// 請求二完成
// 請求一完成
// 刷新頁面
--------------------輸出結果:-------------------
複製代碼
調度組要注意搭配使用,必須先進組再出組,缺一不可
dispatch_group_wait
使用long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)
DISPATCH_TIME_NOW
意味着不等待直接斷定調度組是否執行完畢DISPATCH_TIME_FOREVER
則會阻塞當前調度組,直到調度組執行完畢long
類型
將上述調度組代碼進行改寫
- (void)test {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"請求一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"請求二完成");
dispatch_group_leave(group);
});
long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
// long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
NSLog(@"timeout=%ld", timeout);
if (timeout == 0) {
NSLog(@"按時完成任務");
} else {
NSLog(@"超時");
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新頁面");
});
}
--------------------輸出結果:-------------------
// timeout=49
// 請求一完成
// 請求二完成
// 超時
// 刷新頁面
--------------------輸出結果:-------------------
複製代碼
應用場景:同步鎖
併發執行異步隊列
會開闢線程,而任務也會由於任務複雜度和cpu的調度致使各個亂序執行完畢,好比上圖中的
任務3
明明是先於
任務4
執行,可是晚於
任務4
執行完畢
此時GCD就提供了兩個API——dispatch_barrier_sync
和dispatch_barrier_async
,使用這兩個API就能將多個任務進行分組——等柵欄前追加到隊列中的任務執行完畢後,再將柵欄後的任務追加到隊列中。簡而言之,就是先執行柵欄前任務
,再執行柵欄任務
,最後執行柵欄後任務
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
NSLog(@"開始——%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延遲2s的任務1——%@", [NSThread currentThread]);
});
NSLog(@"第一次結束——%@", [NSThread currentThread]);
// dispatch_barrier_async(queue, ^{
// NSLog(@"----------柵欄任務----------%@", [NSThread currentThread]);
// });
// NSLog(@"柵欄結束——%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"延遲1s的任務2——%@", [NSThread currentThread]);
});
NSLog(@"第二次結束——%@", [NSThread currentThread]);
}
複製代碼
不使用柵欄函數
開始——<NSThread: 0x600001068900>{number = 1, name = main}
第一次結束——<NSThread: 0x600001068900>{number = 1, name = main}
第二次結束——<NSThread: 0x600001068900>{number = 1, name = main}
延遲2s的任務1——<NSThread: 0x600001025ec0>{number = 3, name = (null)}
延遲1s的任務2——<NSThread: 0x600001025ec0>{number = 3, name = (null)}
複製代碼
使用柵欄函數
開始——<NSThread: 0x6000001bcf00>{number = 1, name = main}
第一次結束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
柵欄結束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
第二次結束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
延遲2s的任務1——<NSThread: 0x6000001fcf00>{number = 5, name = (null)}
----------柵欄任務----------<NSThread: 0x6000001bcf00>{number = 1, name = main}
延遲1s的任務2——<NSThread: 0x6000001fcf00>{number = 5, name = (null)}
複製代碼
柵欄函數的做用是將隊列中的任務進行分組,因此咱們只要關注任務1
、任務2
串行隊列異步執行
任務是一個接一個執行完畢的,因此使用柵欄函數沒意義
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"開始——%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延遲2s的任務1——%@", [NSThread currentThread]);
});
NSLog(@"第一次結束——%@", [NSThread currentThread]);
// dispatch_barrier_async(queue, ^{
// NSLog(@"----------柵欄任務----------%@", [NSThread currentThread]);
// });
// NSLog(@"柵欄結束——%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"延遲1s的任務2——%@", [NSThread currentThread]);
});
NSLog(@"第二次結束——%@", [NSThread currentThread]);
}
複製代碼
不使用柵欄函數
開始——<NSThread: 0x600002384f00>{number = 1, name = main}
第一次結束——<NSThread: 0x600002384f00>{number = 1, name = main}
第二次結束——<NSThread: 0x600002384f00>{number = 1, name = main}
延遲1s的任務2——<NSThread: 0x6000023ec300>{number = 5, name = (null)}
延遲2s的任務1——<NSThread: 0x60000238c180>{number = 7, name = (null)}
複製代碼
使用柵欄函數
開始——<NSThread: 0x600000820bc0>{number = 1, name = main}
第一次結束——<NSThread: 0x600000820bc0>{number = 1, name = main}
柵欄結束——<NSThread: 0x600000820bc0>{number = 1, name = main}
第二次結束——<NSThread: 0x600000820bc0>{number = 1, name = main}
延遲2s的任務1——<NSThread: 0x600000863c80>{number = 4, name = (null)}
----------柵欄任務----------<NSThread: 0x600000863c80>{number = 4, name = (null)}
延遲1s的任務2——<NSThread: 0x600000863c80>{number = 4, name = (null)}
複製代碼
併發隊列異步執行
任務是亂序執行完畢的,因此使用柵欄函數能夠很好的控制隊列內任務執行的順序
dispatch_barrier_async
:前面的任務執行完畢纔會來到這裏dispatch_barrier_sync
:做用相同,可是這個會堵塞線程,影響後面的任務執行將案例二中的dispatch_barrier_async
改爲dispatch_barrier_sync
開始——<NSThread: 0x600001040d40>{number = 1, name = main}
第一次結束——<NSThread: 0x600001040d40>{number = 1, name = main}
延遲2s的任務1——<NSThread: 0x60000100ce40>{number = 6, name = (null)}
----------柵欄任務----------<NSThread: 0x600001040d40>{number = 1, name = main}
柵欄結束——<NSThread: 0x600001040d40>{number = 1, name = main}
第二次結束——<NSThread: 0x600001040d40>{number = 1, name = main}
延遲1s的任務2——<NSThread: 0x60000100ce40>{number = 6, name = (null)}
複製代碼
結論:dispatch_barrier_async能夠控制隊列中任務的執行順序,而dispatch_barrier_sync不只阻塞了隊列的執行,也阻塞了線程的執行(儘可能少用)
儘可能使用自定義的併發隊列
:
全局隊列
起不到柵欄函數
的做用全局隊列
時因爲對全局隊列形成堵塞,可能導致系統其餘調用全局隊列的地方也堵塞從而致使崩潰(並非只有你在使用這個隊列)柵欄函數只能控制同一併發隊列
:打個比方,平時在使用AFNetworking作網絡請求時爲何不能用柵欄函數起到同步鎖堵塞的效果,由於AFNetworking內部有本身的隊列應用場景:同步當鎖, 控制GCD最大併發數
dispatch_semaphore_create()
:建立信號量dispatch_semaphore_wait()
:等待信號量,信號量減1。當信號量< 0
時會阻塞當前線程,根據傳入的等待時間決定接下來的操做——若是永久等待將等到信號(signal)
才執行下去dispatch_semaphore_signal()
:釋放信號量,信號量加1。當信號量>= 0
會執行wait以後的代碼下面這段代碼要求使用信號量來按序輸出(固然柵欄函數能夠知足要求)
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"當前%d----線程%@", i, [NSThread currentThread]);
});
// 使用柵欄函數
// dispatch_barrier_async(queue, ^{});
}
}
複製代碼
利用信號量的API來進行代碼改寫
- (void)test {
// 建立信號量
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"當前%d----線程%@", i, [NSThread currentThread]);
// 打印任務結束後信號量解鎖
dispatch_semaphore_signal(sem);
});
// 因爲異步執行,打印任務會較慢,因此這裏信號量加鎖
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
}
複製代碼
輸出結果
當前0----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前1----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前2----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前3----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前4----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前5----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前6----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前7----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前8----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前9----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
複製代碼
若是當建立信號量時傳入值爲1又會怎麼樣呢?
i=0
時有可能先打印
,也可能會先發出wait
信號量-1,可是wait
以後信號量爲0不會阻塞線程,因此進入i=1
i=1
時有可能先打印
,也可能會先發出wait
信號量-1,可是wait
以後信號量爲-1阻塞線程,等待signal
再執行下去當前1----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前0----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前2----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前3----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前4----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前5----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前6----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前7----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前8----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前9----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
複製代碼
結論:
應用場景:GCDTimer
dispatch_source
是一種基本的數據類型,能夠用來監聽一些底層的系統事件
Timer Dispatch Source
:定時器事件源,用來生成周期性的通知或回調Signal Dispatch Source
:監聽信號事件源,當有UNIX信號發生時會通知Descriptor Dispatch Source
:監聽文件或socket事件源,當文件或socket數據發生變化時會通知Process Dispatch Source
:監聽進程事件源,與進程相關的事件通知Mach port Dispatch Source
:監聽Mach端口事件源Custom Dispatch Source
:監聽自定義事件源主要使用的API:
dispatch_source_create
: 建立事件源dispatch_source_set_event_handler
: 設置數據源回調dispatch_source_merge_data
: 設置事件源數據dispatch_source_get_data
: 獲取事件源數據dispatch_resume
: 繼續dispatch_suspend
: 掛起dispatch_cancle
: 取消在iOS開發中通常使用NSTimer
來處理定時邏輯,但NSTimer
是依賴Runloop
的,而Runloop
能夠運行在不一樣的模式下。若是NSTimer
添加在一種模式下,當Runloop運行在其餘模式下的時候,定時器就掛機了;又若是Runloop
在阻塞狀態,NSTimer
觸發時間就會推遲到下一個Runloop
週期。所以NSTimer
在計時上會有偏差,並非特別精確,而GCD定時器不依賴Runloop
,計時精度要高不少
@property (nonatomic, strong) dispatch_source_t timer;
//1.建立隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.建立timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.設置timer首次執行時間,間隔,精確度
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//4.設置timer事件回調
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCDTimer");
});
//5.默認是掛起狀態,須要手動激活
dispatch_resume(_timer);
複製代碼
使用dispatch_source
自定義定時器注意點:
GCDTimer
須要強持有
,不然出了做用域當即釋放,也就沒有了事件回調GCDTimer
默認是掛起狀態,須要手動激活GCDTimer
沒有repeat
,須要封裝來增長標誌位控制GCDTimer
若是存在循環引用,使用weak+strong
或者提早調用dispatch_source_cancel
取消timerdispatch_resume
和dispatch_suspend
調用次數須要平衡source
在掛起狀態
下,若是直接設置source = nil
或者從新建立source
都會形成crash。正確的方式是在激活狀態
下調用dispatch_source_cancel(source)
釋放當前的source
API | 說明 |
---|---|
dispatch_sync() | 同步執行 |
dispatch_async() | 異步執行 |
dispatch_queue_create() | 建立隊列 |
dispatch_get_main_queue() | 獲取主隊列 |
dispatch_get_global_queue() | 獲取全局隊列 |
dispatch_after() | 延時執行 |
dispatch_once() | 一次性執行 |
dispatch_apply() | 提交隊列 |
dispatch_group_create() | 建立調度組 |
dispatch_group_async() | 執行進組任務 |
dispatch_group_enter()/ dispatch_group_leave() |
將調度組中的任務未執行完畢的任務數目加減1 (兩個函數要配合使用) |
dispatch_group_wait() | 設置等待時間(成功爲0) |
dispatch_barrier_sync() | 同步柵欄函數 |
dispatch_barrier_async() | 異步柵欄函數 |
dispatch_group_notify() | 監聽隊列組執行完畢 |
dispatch_semaphore_creat() | 建立信號量 |
dispatch_semaphore_wait() | 等待信號量 |
dispatch_semaphore_signal() | 釋放信號量 |
dispatch_source_create | 建立源 |
dispatch_source_set_event_handler | 設置源事件回調 |
dispatch_source_merge_data | 源事件設置數據 |
dispatch_source_get_data | 獲取源事件數據 |
dispatch_resume | 繼續 |
dispatch_suspend | 掛起 |
dispatch_cancle | 取消 |
下篇文章將探索GCD的底層原理,GCD應用中的部分坑點會在多線程面試題中進行補充