GCD(三) dispatch_group

本文是GCD多線程編程中dispatch_group內容的小結,經過本文,你能夠了解到:ios

  • 如何使用dispatch_group來實如今一系列併發任務完成後作一些收尾工做的需求

咱們在日常的開發中,常常會遇到這樣這樣的一個需求,當應用程序啓動時,須要從服務器獲取各類配置信息,而後再去作首頁UI的初始化與後面的邏輯處理。對於這個需求,咱們確定是但願能夠調用一個方法來執行這些任務,並在全部網絡請求完成後調用已完成的回調,用於後續UI的的初始化。git

面對這種場景,咱們可使用一種最直接的方式,就是從第一個網絡請求的回調中繼續下一個網絡請求,可是這樣實現的話,咱們的代碼就會像這種:github

}];
                }];
            }];
        }];
    }];
複製代碼

這種方式雖然也一樣能夠實現需求,可是在編碼上不夠優雅,沒有太強的閱讀性,其實,這種須要等待一系列併發任務完成,而後在完成以後作一些收尾的工做的需求,GCD已經提供了一組API,就是使用dispatch_group來作,dispatch_group的使用分爲如下幾步:macos

  1. 建立dispatch_group
  2. 添加任務(併發)到group中
  3. 添加監聽group中任務結束時的回調

接下來,咱們來具體看看dispatch_group相關的API與基本使用編程

測試代碼在這bash

1、建立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();
複製代碼

2、添加任務到dispatch_group

添加任務有2種方式:session

  • 第一種是使用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_enterdispatch_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中任務執行完成的指令永遠不會調用。

3、添加監聽group中任務結束時的回調

這裏也有2種方式,dispatch_group_waitdispatch_group_notify

dispatch_group_wait

這種方式會阻塞當前的線程,直到group中的任務所有完成,程序纔會繼續往下執行。

dispatch_group_notify

這種方式是添加一個異步執行的任務做爲結束任務,當group中的任務所有完成,纔會執行dispatch_group_notify中添加的異步任務,這種方式不會阻塞當前線程,同時有一個單獨的異步回調,代碼組織性更好,使用也更新普遍一些。

4、代碼實戰 & 使用小結

接下來,咱們看看完整的測試代碼:

- (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_asyncdispatch_group_enter都是異步添加任務,不會阻塞當前線程

dispatch_group_notify不會阻塞當前線程,dispatch_group_wait會阻塞當前線程

dispatch_group_enterdispatch_group_leave必須成對出現,不然group中的任務永遠不會完成

若是文中有錯誤的地方,或者與你的想法相悖的地方,請在評論區告知我,我會繼續改進,若是你以爲這個篇文章總結的還不錯,麻煩動動小手,給個人文章與Git代碼樣例點個✨

相關文章
相關標籤/搜索