dispatch_once
單次函數通常用來建立單例或者是執行只須要執行一次的程序。bash
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"==只會執行一次的代碼==");
});
void dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block)
複製代碼
dispatch_once
會保證block
中的程序只執行一次,而且即便在多線程的環境下,dispatch_once
也能夠保證線程安全。多線程
dispatch_apply
dispatch_apply
函數會按照指定的次數將指任務添加到指定的隊列中進行執行。不管是在串行隊列,仍是併發隊列中,dispatch_apply
都會等待所有任務執行完畢。閉包
若是是在串行隊列中使用dispatch_apply
,會按順序同步執行,就和普通的for
循環相似;若是是在異步隊列中使用,下標可能不是按順序來的。併發
void
dispatch_apply(size_t iterations,
dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
DISPATCH_NOESCAPE void (^block)(size_t));
複製代碼
iterations
:執行迭代的次數queue
:執行迭代的隊列,建議使用DISPATCH_APPLY_AUTO
,會自動調用合適的線程隊列void (^block)(size_t))
:迭代的結果回調dispatch_after
延遲函數的做用是在指定的隊列中,按照給定的時間執行一個操做。app
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
dispatch_block_t block);
複製代碼
dispatch_time_t when
:指定執行任務的時間。
DISPATCH_TIME_NOW
,可是不推薦,由於該函數調用了dispatch_async
dispatch_time
或者dispatch_walltime
自定義時間:dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC))
DISPATCH_TIME_FOREVER
dispatch_queue_t queue
:指定隊列,執行任務的隊列。dispatch_block_t block
:要執行的任務,不能傳NULL
。dispatch_group
經過Dispatch Group
,咱們能夠將多個任務放入一個組中,而且可讓他們在同一隊列或不一樣隊列上異步執行,執行完成以後,再執行其餘的依賴於這些任務的操做。異步
相關API
:async
dispatch_group_t dispatch_group_create(void);
複製代碼
void dispatch_group_enter(dispatch_group_t group);
複製代碼
void dispatch_group_leave(dispatch_group_t group);
複製代碼
timeout
歸零纔會繼續下一步long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
複製代碼
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
複製代碼
下面咱們經過一個例子來看一下dispatch_group
的使用:函數
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"==1==");
});
dispatch_async(queue, ^{
NSLog(@"==2==");
});
dispatch_async(queue, ^{
NSLog(@"==3==");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"===4=");
});
複製代碼
運行程序,控制檯輸出:post
能夠看出這並非咱們想要的結果。對程序進行修改,繼續運行:
一樣使用dispatch_group_wait
也會獲得相應的結果:
可是dispatch_group_wait
會阻塞以後的操做,好比咱們在組通知以後還執行了NSLog(@"==5==")
,組任務並無阻塞到它的執行,而dispatch_group_wait
就會阻塞。
注意,dispatch_group_enter
和dispatch_group_leave
必須成對出現,不然會形成死鎖。
dispatch_barrier
柵欄函數分爲dispatch_barrier_async
和dispatch_barrier_sync
函數,這兩個函數既有共同點,又有不一樣點:
dispatch_barrier_sync
將本身的任務插入到隊列的時候,須要等待本身的任務結束以後纔會繼續插入被寫在它後面的任務,而後執行它們dispatch_barrier_async
將本身的任務插入到隊列以後,不會等待本身的任務結束,它會繼續把後面的任務插入到隊列,而後等待本身的任務結束後才執行後面任務。下面咱們配合一個例子說明一下:
- (void)barrierAsync {
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"--1--");
});
dispatch_async(queue, ^{
NSLog(@"--2--");
});
dispatch_barrier_async(queue, ^{
NSLog(@"--barrier_async--%@--",[NSThread currentThread]);
sleep(2);
});
NSLog(@"=======barrierAsync=======");
dispatch_async(queue, ^{
NSLog(@"--3--");
});
dispatch_async(queue, ^{
NSLog(@"--4--");
});
dispatch_async(queue, ^{
NSLog(@"--5--");
});
}
複製代碼
運行程序:
將dispatch_barrier_async
函數改成dispatch_barrier_sync
,而後運行程序:
經過打印結果能夠看出柵欄函數不論是同步異步,都會對當前隊列中的任務起到隔離做用,就是會讓柵欄以前的多線程操做先執行,讓柵欄以後的多線程操做後執行。不一樣的是dispatch_barrier_async
函數以後的多線程操做都是併發執行,而dispatch_barrier_sync
以後的操做都是同步執行,因此咱們打印的barrierAsync
的執行順序和barrierSync
不一樣。
簡而言之,dispatch_barrier_sync
和dispatch_barrier_async
都會隔離隊列中柵欄先後的任務,不一樣的是會不會阻塞當前隊列。因此柵欄函數和其攔截的任務必須是同一隊列的,否則沒有阻塞效果。因此在AFN
中使用柵欄函數沒有效果,AFN
本身維護了一個串行隊裏,除非使用這個隊列纔會起做用。
注意,當咱們在主線程中調用任務,並且將同步柵欄函數也添加到主隊列中,會發生死鎖現象。使用柵欄函數要使用自定義隊列,防止阻塞、死鎖。
dispatch_semaphore_t
一種可用來控制訪問資源的數量的標識,設定了一個信號量,在線程訪問以前,加上信號量的處理,則可告知系統按照咱們指定的信號量數量來執行多個線程。
相關API
:
NULL
,該參數控制當前能開啓的線程數量。dispatch_semaphore_t dispatch_semaphore_create(long value)
複製代碼
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
複製代碼
dispatch_semaphore_t dsema
: 信號量。若是傳入的dsema
大於0,就繼續向下執行,並將信號量減1;若是dsema
等於0,阻塞當前線程等待資源被dispatch_semaphore_signal
釋放。若是等到了信號量,繼續向下執行並將信號量減1,若是一直沒有等到信號量,就等到timeout
再繼續執行。
dispatch_time_t timeout
: 超時,阻塞線程的時長。通常傳DISPATCH_TIME_FOREVER
或者DISPATCH_TIME_NOW
,也能夠自定義。dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 1*100*100*100);
若是成功則返回0,超時會返回其餘值
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
複製代碼
減小和增長信號量一般成對使用,使用的順序是先減小信號量(wait)
而後再增長信號量(signal)
。
下面咱們結合一個例子,說明一下信號量的使用:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//任務1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"執行任務1");
sleep(1);
NSLog(@"任務1完成");
dispatch_semaphore_signal(semaphore);
});
//任務2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"執行任務2");
sleep(1);
NSLog(@"任務2完成");
dispatch_semaphore_signal(semaphore);
});
//任務3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"執行任務3");
sleep(1);
NSLog(@"任務3完成");
dispatch_semaphore_signal(semaphore);
});
複製代碼
運行程序,控制檯輸出:
將建立的信號量改成2:dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
複製代碼
將建立的信號量改成3,或者大於3:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
複製代碼
同理,咱們還能夠將例子中的併發任務改成同步任務。能夠得出以下結論:
再來看一個例子:
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
});
}
NSLog(@"==a==%d==", a);
複製代碼
因爲異步線程的問題,咱們打印a
的值,多是大於等於5,此時依靠信號量就能夠控制讓循環外輸出a=5
。以下:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
NSLog(@"==a==%d==", a);
複製代碼
關於信號量的時候,咱們須要注意的是防止線程被阻塞,當執行dispatch_semaphore_wait
方法的時候必定要保證傳入的信號量大於0。
dispatch_source
當有一些特定的較底層的系統事件發生時,調度源會捕捉到這些事件,而後能夠作其餘的邏輯處理,調度源有多種類型,分別監聽對應類型的系統事件。也就是用GCD
的函數指定一個但願監聽的系統事件類型,再指定一個捕獲到事件後進行邏輯處理的閉包或者函數做爲回調函數,而後再指定一個該回調函數執行的隊列便可,當監聽到指定的系統事件發生時會調用回調函數,將該回調函數做爲一個任務放入指定的隊列中執行。
相關的API
:
dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t _Nullable queue);
複製代碼
void
dispatch_source_set_event_handler(dispatch_source_t source,
dispatch_block_t _Nullable handler);
複製代碼
void
dispatch_source_merge_data(dispatch_source_t source, unsigned long value);
複製代碼
unsigned long
dispatch_source_get_data(dispatch_source_t source);
複製代碼
獲取的數據類型和源事件的類型相關:
dispatch_source
,返回的是讀到文件內容的字節數。dispatch_source
,返回的是文件是否可寫的標識符,正數表示可寫,負數表示不可寫。dispatch_source
,返回的是監聽到的有更改的文件屬性,用常量表示,好比DISPATCH_VNODE_RENAME
等。dispatch_source
,返回監聽到的進程狀態,用常量表示,好比DISPATCH_PROC_EXIT
等。Mach
端口類型的dispatch_source
,返回Mach
端口的狀態,用常量表示,好比DISPATCH_MACH_SEND_DEAD
等。dispatch_source
,返回使用dispatch_source_merge_data
函數設置的數據。void
dispatch_resume(dispatch_object_t object);
複製代碼
void
dispatch_suspend(dispatch_object_t object);
複製代碼
dispatch_source_type_t type
:設置dispatch_source
方法的類型uintptr_t handle
:取決於要監聽的事件類型,好比若是是監聽Mach
端口相關的事件,那麼該參數就是mach_port_t
類型的Mach
端口號,若是是監聽事件變量數據類型的事件那麼該參數就不須要,設置爲0就能夠了。unsigned long mask
:取決於要監聽的事件類型dispatch_queue_t _Nullable queue
:執行的隊列,默認爲全局隊列dispatch_source_type_t
的取值以下:
DISPATCH_SOURCE_TYPE_DATA_ADD
:屬於自定義事件,能夠經過dispatch_source_get_data
函數獲取事件變量數據,在咱們自定義的方法中能夠調用dispatch_source_merge_data
函數向dispatch_source
設置數據。DISPATCH_SOURCE_TYPE_DATA_OR
:屬於自定義事件,用法同DISPATCH_SOURCE_TYPE_DATA_ADD
。DISPATCH_SOURCE_TYPE_MACH_SEND
:Mach
端口發送事件。DISPATCH_SOURCE_TYPE_MACH_RECV
:Mach
端口接收事件。DISPATCH_SOURCE_TYPE_PROC
:與進程相關的事件。DISPATCH_SOURCE_TYPE_READ
:讀文件事件。DISPATCH_SOURCE_TYPE_WRITE
:寫文件事件。DISPATCH_SOURCE_TYPE_VNODE
:文件屬性更改事件。DISPATCH_SOURCE_TYPE_SIGNAL
:接收信號事件。DISPATCH_SOURCE_TYPE_TIMER
:定時器事件。DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
:內存壓力事件。下面咱們結合一個例子,具體的說明一下使用:
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, assign) NSUInteger totalComplete;
- (void)initSource {
self.queue = dispatch_queue_create("soureQueue", 0);
// 建立soure事件
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// 監聽soure事件發生變化
dispatch_source_set_event_handler(self.source, ^{
// 獲取source事件的值
NSUInteger value = dispatch_source_get_data(self.source);
self.totalComplete += value;
NSLog(@"進度:%.2f", self.totalComplete/100.0);
});
// 啓動監聽
dispatch_resume(self.source);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
for (NSUInteger index = 0; index < 100; index++) {
dispatch_async(self.queue, ^{
sleep(1);
// 設置source事件的數據
dispatch_source_merge_data(self.source, 1);
});
}
}
複製代碼
運行程序:
dispatch_once
dispatch_after
是異步執行的dispatch_apply
dispatch_group
dispatch_group_enter
和dispatch_group_leave
必須成對出現,不然會形成死鎖enter
後leave
dispatch_group_wait
會阻塞當前線程dispatch_barrier
dispatch_semaphore
dispatch_semaphore_wait
的參數爲0的時候會堵塞線程dispatch_source
dispatch_source
的基本操做source
的類型參考資料:
官方文檔
iOS 多線程:『GCD』詳盡總結