GCD(三) dispatch_group
本文是GCD多執行緒程式設計中dispatch_group
內容的小結,通過本文,你可以瞭解到:
-
如何使用
dispatch_group
來實現在一系列併發任務完成後做一些收尾工作的需求
我們在平常的開發中,經常會遇到這樣這樣的一個需求,當應用程式啟動時,需要從伺服器獲取各種配置資訊,然後再去做首頁UI的初始化與後面的邏輯處理。對於這個需求,我們肯定是希望可以呼叫一個方法來執行這些任務,並在所有網路請求完成後呼叫已完成的回撥,用於後續UI的的初始化。
面對這種場景,我們可以使用一種最直接的方式,就是從第一個網路請求的回撥中繼續下一個網路請求,但是這樣實現的話,我們的程式碼就會像這種:
}]; }]; }]; }]; }]; 複製程式碼
這種方式雖然也同樣可以實現需求,但是在編碼上不夠優雅,沒有太強的閱讀性,其實,這種需要等待一系列併發任務完成,然後在完成之後做一些收尾的工作的需求,GCD已經提供了一組API,就是使用dispatch_group
來做,dispatch_group
的使用分為以下幾步:
dispatch_group
接下來,我們來具體看看dispatch_group
相關的API與基本使用
一、建立dispatch_group
dispatch_group_t
/*! * @typedef dispatch_group_t * @abstract * A group of blocks submitted to queues for asynchronous invocation. */ DISPATCH_DECL(dispatch_group); 複製程式碼
dispatch_group_create
/*! * @function dispatch_group_create * * @abstract * Creates new group with which blocks may be associated. * * @discussion * This function creates a new group with which blocks may be associated. * The dispatch group may be used to wait for the completion of the blocks it * references. The group object memory is freed with dispatch_release(). * * @result * The newly created group, or NULL on failure. */ API_AVAILABLE(macos(10.6), ios(4.0)) DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT DISPATCH_NOTHROW dispatch_group_t dispatch_group_create(void); 複製程式碼
dispatch_group_t
其實就是提交到佇列中用以進行非同步呼叫的一組任務
我們可以使用dispatch_group_create
方法來建立group
dispatch_group_t group = dispatch_group_create(); 複製程式碼
二、新增任務到dispatch_group
新增任務有2種方式:
-
第一種是使用
dispatch_group_async
新增任務到一個特定的佇列 -
第二種是人為的告訴
group
,我們開始了一個任務(dispatch_group_enter
),或者任務結束了(dispatch_group_leave
)
dispatch_group_async
#ifdef __BLOCKS__ API_AVAILABLE(macos(10.6), ios(4.0)) DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block); #endif /* __BLOCKS__ */ 複製程式碼
這個函式有3個引數,第一個是管理這些非同步任務的group,第二個是用於提交非同步任務佇列,第三個是我們提交的任務。
使用這個方法,我們可以定製我們的網路請求任務,新增到對應的併發佇列,然後使用group管理這些任務,這樣我們的非同步併發網路請求的目的就實現了。
但是,我們平時的開發過程中,我們的網路請求基本上都是需要非同步新增任務的,無法直接使用佇列,這時我們就可以使用dispatch_group_enter與
dispatch_group_leave`這一對API
dispatch_group_enter/leave
/*! * @function dispatch_group_enter * * @abstract * Manually indicate a block has entered the group * * @discussion * Calling this function indicates another block has joined the group through * a means other than dispatch_group_async(). Calls to this function must be * balanced with dispatch_group_leave(). * * @param group * The dispatch group to update. * The result of passing NULL in this parameter is undefined. */ API_AVAILABLE(macos(10.6), ios(4.0)) DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW void dispatch_group_enter(dispatch_group_t group); /*! * @function dispatch_group_leave * * @abstract * Manually indicate a block in the group has completed * * @discussion * Calling this function indicates block has completed and left the dispatch * group by a means other than dispatch_group_async(). * * @param group * The dispatch group to update. * The result of passing NULL in this parameter is undefined. */ API_AVAILABLE(macos(10.6), ios(4.0)) DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW void dispatch_group_leave(dispatch_group_t group); 複製程式碼
dispatch_group_enter
表示這個任務已經新增到group中
dispatch_group_leave
表示新增到group中的這個任務已經執行完成
我們經常會使用這組API,將一些非同步的網路請求的任務包裝起來放進group中(早版本的AFNetworking中執行非同步任務):
NSLog(@"使用dispatch_group_enter方式追加任務3"); dispatch_group_enter(self.group); //開啟一個網路請求 NSURLSession *session = [NSURLSession sharedSession]; NSURL *url = [NSURL URLWithString:[@"https://www.baidu.com/" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"GET"; NSLog(@"3---start---%@",[NSThread currentThread]); NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error) { NSLog(@"%@", [error localizedDescription]); } if (data) { NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; NSLog(@"%@", dict); } NSLog(@"3---end---%@",[NSThread currentThread]); dispatch_group_leave(self.group); }]; [dataTask resume]; 複製程式碼
這組API必須配對呼叫,否則,group中任務執行完成的指令永遠不會呼叫。
三、新增監聽group中任務結束時的回撥
這裡也有2種方式,dispatch_group_wait
與dispatch_group_notify
dispatch_group_wait
這種方式會阻塞當前的執行緒,直到group中的任務全部完成,程式才會繼續往下執行。
dispatch_group_notify
這種方式是新增一個非同步執行的任務作為結束任務,當group中的任務全部完成,才會執行dispatch_group_notify
中新增的非同步任務,這種方式不會阻塞當前執行緒,同時有一個單獨的非同步回撥,程式碼組織性更好,使用也更新廣泛一些。
四、程式碼實戰 & 使用小結
接下來,我們看看完整的測試程式碼:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. NSLog(@"ZEDDispatchGroupViewController viewDidLoad"); //第一步:建立group NSLog(@"初始化group"); self.group = dispatch_group_create(); //第二步:追加任務到group NSLog(@"使用dispatch_group_async方式追加任務1"); dispatch_group_async(self.group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2];// 模擬耗時操作 NSLog(@"1---%@",[NSThread currentThread]);// 列印當前執行緒 } NSLog(@"任務1完成"); }); NSLog(@"使用dispatch_group_async方式追加任務2"); dispatch_group_async(self.group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2];// 模擬耗時操作 NSLog(@"2---%@",[NSThread currentThread]);// 列印當前執行緒 } NSLog(@"任務2完成"); }); NSLog(@"使用dispatch_group_enter方式追加任務3"); //dispatch_group_enter與dispatch_group_leave必須成對出現 dispatch_group_enter(self.group); //開啟一個網路請求 NSURLSession *session = [NSURLSession sharedSession]; NSURL *url = [NSURL URLWithString:[@"https://www.baidu.com/" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"GET"; NSLog(@"3---start---%@",[NSThread currentThread]); NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error) { NSLog(@"%@", [error localizedDescription]); } if (data) { NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; NSLog(@"%@", dict); } NSLog(@"3---end---%@",[NSThread currentThread]); NSLog(@"任務3完成"); dispatch_group_leave(self.group); }]; [dataTask resume]; //第三步:新增group中任務全部完成的回撥 NSLog(@"使用dispatch_group_notify新增非同步任務全部完成的監聽"); //dispatch_group_notify 的方式不會阻塞當前執行緒 dispatch_group_notify(self.group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"---所有任務全部執行完畢---"); }); //dispatch_group_wai會阻塞當前執行緒,直到group中的任務全部完成,才能繼續往主佇列中追加任務 //dispatch_group_wait(self.group, DISPATCH_TIME_FOREVER); NSLog(@"---測試結束了---"); } 複製程式碼
測試結果log如下:
2019-04-25 17:07:21.432220+0800 GCD(三) dispatch_group[28759:5272759] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform. 2019-04-25 17:07:21.543885+0800 GCD(三) dispatch_group[28759:5272759] ZEDDispatchGroupViewController viewDidLoad 2019-04-25 17:07:21.544044+0800 GCD(三) dispatch_group[28759:5272759] 初始化group 2019-04-25 17:07:21.544161+0800 GCD(三) dispatch_group[28759:5272759] 使用dispatch_group_async方式追加任務1 2019-04-25 17:07:21.544286+0800 GCD(三) dispatch_group[28759:5272759] 使用dispatch_group_async方式追加任務2 2019-04-25 17:07:21.544391+0800 GCD(三) dispatch_group[28759:5272759] 使用dispatch_group_enter方式追加任務3 2019-04-25 17:07:21.547318+0800 GCD(三) dispatch_group[28759:5272759] 3---start---<NSThread: 0x600002f86c00>{number = 1, name = main} 2019-04-25 17:07:21.548050+0800 GCD(三) dispatch_group[28759:5272759] 使用dispatch_group_notify新增非同步任務全部完成的監聽 2019-04-25 17:07:21.548173+0800 GCD(三) dispatch_group[28759:5272759] ---測試結束了--- 2019-04-25 17:07:21.700314+0800 GCD(三) dispatch_group[28759:5272797] (null) 2019-04-25 17:07:21.700490+0800 GCD(三) dispatch_group[28759:5272797] 3---end---<NSThread: 0x600002fe0940>{number = 5, name = (null)} 2019-04-25 17:07:21.700611+0800 GCD(三) dispatch_group[28759:5272797] 任務3完成 2019-04-25 17:07:23.547004+0800 GCD(三) dispatch_group[28759:5272796] 1---<NSThread: 0x600002fde480>{number = 6, name = (null)} 2019-04-25 17:07:23.547076+0800 GCD(三) dispatch_group[28759:5272798] 2---<NSThread: 0x600002fde4c0>{number = 7, name = (null)} 2019-04-25 17:07:25.547612+0800 GCD(三) dispatch_group[28759:5272798] 2---<NSThread: 0x600002fde4c0>{number = 7, name = (null)} 2019-04-25 17:07:25.547634+0800 GCD(三) dispatch_group[28759:5272796] 1---<NSThread: 0x600002fde480>{number = 6, name = (null)} 2019-04-25 17:07:25.547901+0800 GCD(三) dispatch_group[28759:5272796] 任務1完成 2019-04-25 17:07:25.547910+0800 GCD(三) dispatch_group[28759:5272798] 任務2完成 2019-04-25 17:07:25.548138+0800 GCD(三) dispatch_group[28759:5272798] ---所有任務全部執行完畢--- 複製程式碼
dispatch_group_async
與dispatch_group_enter
都是非同步新增任務,不會阻塞當前執行緒
dispatch_group_notify
不會阻塞當前執行緒,dispatch_group_wait
會阻塞當前執行緒
dispatch_group_enter
與dispatch_group_leave
必須成對出現,否則group中的任務永遠不會完成
如果文中有錯誤的地方,或者與你的想法相悖的地方,請在評論區告知我,我會繼續改進,如果你覺得這個篇文章總結的還不錯,麻煩動動小手,給我的文章與Git程式碼樣例
點個:sparkles: