iOS:GCD【dispatch_semaphore】使用之所思

最近在使用GCD的時候,發(fā)現(xiàn)自己對dispatch_semaphore理解不是那么深刻,所以自己在網(wǎng)上找資料學習dispatch_semaphore,并對dispatch_semaphore有了一些自己的看法和理解。

1.異步任務使用semaphore

    NSLog(@"----------------------------開始----------------------------  線程:%@",[NSThread currentThread]);
    dispatch_semaphore_t sema =  dispatch_semaphore_create(0);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"---------------------------- 任務一開始 ---------------------------- 線程:%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"---------------------------- 任務一結(jié)束 ---------------------------- 線程:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sema);
    });
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"---------------------------- 任務二開始 ---------------------------- 線程:%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"---------------------------- 任務二結(jié)束 ---------------------------- 線程:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sema);
    });
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"---------------------------- 任務三開始 ---------------------------- 線程:%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"---------------------------- 任務三結(jié)束 ---------------------------- 線程:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sema);
    });
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    
    NSLog(@"----------------------------結(jié)束---------------------------- 線程:%@",[NSThread currentThread]);

打印結(jié)果如下:

2018-11-20 10:05:48.165624+0800 Demo[2920:724653] ----------------------------開始----------------------------  線程:<NSThread: 0x282c6b180>{number = 1, name = main}
2018-11-20 10:05:48.166227+0800 Demo[2920:724705] ---------------------------- 任務一開始 ---------------------------- 線程:<NSThread: 0x282cebc40>{number = 3, name = (null)}
2018-11-20 10:05:50.171470+0800 Demo[2920:724705] ---------------------------- 任務一結(jié)束 ---------------------------- 線程:<NSThread: 0x282cebc40>{number = 3, name = (null)}
2018-11-20 10:05:50.171953+0800 Demo[2920:724705] ---------------------------- 任務二開始 ---------------------------- 線程:<NSThread: 0x282cebc40>{number = 3, name = (null)}
2018-11-20 10:05:52.177195+0800 Demo[2920:724705] ---------------------------- 任務二結(jié)束 ---------------------------- 線程:<NSThread: 0x282cebc40>{number = 3, name = (null)}
2018-11-20 10:05:52.177657+0800 Demo[2920:724710] ---------------------------- 任務三開始 ---------------------------- 線程:<NSThread: 0x282ce9380>{number = 4, name = (null)}
2018-11-20 10:05:54.182973+0800 Demo[2920:724710] ---------------------------- 任務三結(jié)束 ---------------------------- 線程:<NSThread: 0x282ce9380>{number = 4, name = (null)}
2018-11-20 10:05:54.183224+0800 Demo[2920:724653] ----------------------------結(jié)束---------------------------- 線程:<NSThread: 0x282c6b180>{number = 1, name = main}

這里我們可以分析到,semaphore確實起到了阻塞等待的作用。dispatch_semaphore_t sema = dispatch_semaphore_create(0)首先創(chuàng)建了一個信號量為0的semaphore,接著開啟了一個異步任務,在任務中dispatch_semaphore_signal(sema)會使信號量+1,然后在主線程中dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)使信號量-1,此時信號量為-1,那么會一直等待。所以主線程會一直等待,直到異步任務執(zhí)行完畢dispatch_semaphore_signal(sema)這里,對信號量+1,然后才會接著往下走。后面的任務以此類推。

可是在我們平常的使用中,更多是配合網(wǎng)絡請求。例如請求B的數(shù)據(jù)依賴于請求A,只有先拿到了請求A的數(shù)據(jù),才能請求B,當然這個需求解決辦法有很多,但這里我們就只談談怎么用semaphore來解決這個需求。接著我們利用相同思路,結(jié)合AFN使用semaphore:

NSLog(@"----------------- 測試開始 -----------------");
    NSDictionary *dict = @{ @"page": @1,
                            @"pageSize": @20};
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    //對AFN的簡單封裝 請求一個post任務
    [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypePOST
                                            withURLString:@"https://api.it120.cc/tz/shop/goods/list" withParameters:dict
                                         withSuccessBlock:^(id result) {
                                             NSLog(@"----------------- 任務一完成 -----------------%@ -----------------",[NSThread currentThread]);
                                             dispatch_semaphore_signal(semaphore);
                                             
                                         } withFailBlock:^(NSError *error) {
                                             dispatch_semaphore_signal(semaphore);
                                         }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //請求一個get任務
    [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) {
        
        NSLog(@"----------------- 任務二完成 ----------------- %@ -----------------",[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);

    } withFailBlock:^(NSError *error) {
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
 
    NSLog(@"----------------- 測試結(jié)束 -----------------");

打印結(jié)果如下:

2018-11-20 10:05:01.794640+0800 Demo[2918:724320] ----------------- 測試開始 -----------------

這里我們發(fā)現(xiàn),僅僅只打印了測試開始,后面的任務都沒有執(zhí)行。那么究竟是什么原因,導致了主線程的阻塞呢?原來在AFN中,AFN已經(jīng)已經(jīng)將回調(diào)回到了主線程中:

  [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) {
        NSLog(@"----------------- 當前線程:%@ -----------------",[NSThread currentThread]);

    } withFailBlock:^(NSError *error) {
        
    }];

打印結(jié)果如下:
2018-11-20 10:11:32.721403+0800 Demo[2924:725812] ----------------- 當前線程:<NSThread: 0x283e4fa00>{number = 1, name = main} -----------------

而在執(zhí)行到第一個dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)時候,信號量就為0了,將主線程阻塞了,就沒有辦法繼續(xù)往下執(zhí)行dispatch_semaphore_signal(semaphore)。那為什么上面第一種方法可以執(zhí)行呢?是因為上面的任務都是異步的,所以就算主線程阻塞了,在異步任務中還是可以執(zhí)行dispatch_semaphore_signal(semaphore),也就不存在阻塞問題了。
那么我們怎么去解決這樣的問題呢?這里我們就需要用到GCD的另外一種使用方式了:dispatch_group。對dispatch_group不是很了解的小伙伴可以參考這里,里面對GCD的各種使用方法都有很詳細的講解。
好了,廢話不多說,接下來我們看看怎么用dispatch_group完成dispatch_semaphore和AFN的結(jié)合使用:

2.結(jié)合AFN使用dispatch_semaphore

NSLog(@"----------------- 測試開始 -----------------");
    dispatch_group_t gruop = dispatch_group_create();
    dispatch_group_async(gruop, dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        NSLog(@"----------------- gruop:%@ -----------------",[NSThread currentThread]);
        
        [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) {
            
            NSLog(@"----------------- 任務一完成 ----------------- %@ -----------------",[NSThread currentThread]);
            dispatch_semaphore_signal(sema);
            
        } withFailBlock:^(NSError *error) {
            dispatch_semaphore_signal(sema);
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        
        
        [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) {
            
            NSLog(@"----------------- 任務二完成 ----------------- %@ -----------------",[NSThread currentThread]);
            dispatch_semaphore_signal(sema);
            
        } withFailBlock:^(NSError *error) {
            dispatch_semaphore_signal(sema);
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        
        
        [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) {
            
            NSLog(@"----------------- 任務三完成 ----------------- %@ -----------------",[NSThread currentThread]);
            dispatch_semaphore_signal(sema);
            
        } withFailBlock:^(NSError *error) {
            dispatch_semaphore_signal(sema);
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        
    });
    
    dispatch_group_notify(gruop, dispatch_get_main_queue(), ^{
       NSLog(@"----------------- 測試結(jié)束 -----------------");
    });

打印結(jié)果如下:

2018-11-20 10:31:07.634295+0800 Demo[2942:730510] ----------------- 測試開始 -----------------
2018-11-20 10:31:07.636867+0800 Demo[2942:730582] ----------------- gruop:<NSThread: 0x283c8ebc0>{number = 3, name = (null)} -----------------
2018-11-20 10:31:08.057232+0800 Demo[2942:730510] ----------------- 任務一完成 ----------------- <NSThread: 0x283c01b40>{number = 1, name = main} -----------------
2018-11-20 10:31:08.130937+0800 Demo[2942:730510] ----------------- 任務二完成 ----------------- <NSThread: 0x283c01b40>{number = 1, name = main} -----------------
2018-11-20 10:31:08.229891+0800 Demo[2942:730510] ----------------- 任務三完成 ----------------- <NSThread: 0x283c01b40>{number = 1, name = main} -----------------
2018-11-20 10:31:08.230509+0800 Demo[2942:730510] ----------------- 測試結(jié)束 -----------------

根據(jù)打印的結(jié)果,這里完美解決了A任務、B任務之間的依賴關系的需求。也再次證明AFN的回調(diào)確實在主線程中(AFN的源碼中,也可以找到,有興趣的小伙伴可以研究下源碼)。
那么,為什么用到dispatch_group可以解決問題呢?這里dispatch_group_async(gruop, dispatch_get_global_queue(0, 0)開辟一個新的子線程。而在子線程中,dispatch_semaphore_t sema = dispatch_semaphore_create(0)創(chuàng)建了一個信號量為0的semaphore,緊接著在當前子線程中執(zhí)行dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER),那么會阻塞當前線程,一直等待,直到等待AFN的回調(diào)中dispatch_semaphore_signal(sema)將信號量+1。

如果對以上理解了的話,同樣開啟一個異步任務,也可以將任務一、二、三按順序完成:

    NSLog(@"----------------- 測試開始 -----------------");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        
        [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) {
            
            NSLog(@"----------------- 任務一完成 ----------------- %@ -----------------",[NSThread currentThread]);
            dispatch_semaphore_signal(sema);
            
        } withFailBlock:^(NSError *error) {
            dispatch_semaphore_signal(sema);
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        
        
        [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) {
            
            NSLog(@"----------------- 任務二完成 ----------------- %@ -----------------",[NSThread currentThread]);
            dispatch_semaphore_signal(sema);
            
        } withFailBlock:^(NSError *error) {
            dispatch_semaphore_signal(sema);
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        
        
        [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) {
            
            NSLog(@"----------------- 任務三完成 ----------------- %@ -----------------",[NSThread currentThread]);
            dispatch_semaphore_signal(sema);
            
        } withFailBlock:^(NSError *error) {
            dispatch_semaphore_signal(sema);
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        
    });
    NSLog(@"----------------- 測試結(jié)束 -----------------");


打印結(jié)果如下:

2018-11-20 10:38:48.473069+0800 Demo[2950:732087] ----------------- 測試開始 -----------------
2018-11-20 10:38:48.473251+0800 Demo[2950:732087] ----------------- 測試結(jié)束 -----------------
2018-11-20 10:38:48.917159+0800 Demo[2950:732087] ----------------- 任務一完成 ----------------- <NSThread: 0x282351b40>{number = 1, name = main} -----------------
2018-11-20 10:38:49.005818+0800 Demo[2950:732087] ----------------- 任務二完成 ----------------- <NSThread: 0x282351b40>{number = 1, name = main} -----------------
2018-11-20 10:38:49.092094+0800 Demo[2950:732087] ----------------- 任務三完成 ----------------- <NSThread: 0x282351b40>{number = 1, name = main} -----------------

這里可以看到,雖然并不像使用dispatch_gruop那樣,將調(diào)度組中的任務,執(zhí)行完畢后再去調(diào)用
dispatch_group_notify里面的任務。但任務一、二、三卻也是按順序執(zhí)行的。

以上是我個人對dispatch_semaphore的一點點理解,有錯誤的地方希望大家能指出來。

最后,個人還有點小疑問。既然AFN的回調(diào)已經(jīng)在主線程中了,那么為什么我們還要AFN的回調(diào)中用dispatch_get_main_queue()回到主線程更新UI呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容