你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 把握AFNet網絡請求完成的正確時機

把握AFNet網絡請求完成的正確時機

編輯:IOS開發基礎

前言

對於iOS開發中的網絡請求模塊,AFNet的使用應該是最熟悉不過了,但你是否把握了網絡請求正確的完成時機?本篇文章涉及線程同步、線程依賴、線程組等專用名詞的含義,若對上述名詞認識模糊,可先進行查閱理解後閱讀本文。如果你也糾結於文中所述問題,可進行閱讀希望對你有所幫助。大神無視勿噴。

在真實開發中,我們通常會遇到如下問題:

一、某界面存在多個請求,希望所有請求均結束才進行某操作。

對於這一問題的解決方案很容易想到通過線程組進行實現。代碼如下:

dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求1
        NSLog(@"Request_1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求2
        NSLog(@"Request_2");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求3
        NSLog(@"Request_3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任務均完成,刷新界面");
    });

打印如下

Request_2
Request_1
Request_3
任務均完成,刷新界面

根據打印結果觀察可能並沒有什麼問題,但需要注意的是Request_1、Request_2等在真實開發中通常對應為某個網絡請求。而網絡請求通常為異步,那這時是否還會有同樣結果呢?

口說無憑,我們將NSLog(@"Request");部分替換為真正的網絡請求。

對於App請求數據大部分人都會選擇AFNetworking。使用AFN異步請求,請求的數據返回後,就刷新相關UI。如果某一個頁面有多個網絡請求,我們假設有三個請求,A、B、C,而且UI裡的數據必須等到A、B、C全部完成後刷新後才顯示。

這裡我們書寫一個網絡請求通用方法,假設同時請求某新聞列表的3頁數據,每頁均為一個獨立的網絡請求。使用我們最常用的AFNet請求,方法如下(真實開發中可能為banner數據請求、主體網絡請求、廣告網絡請求等):

- (void)request_A {
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];;
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    NSDictionary *parameter = @{@"token":@"63104AB32427EBF89B957BBD1A5C5C11",
                                 @"page":@"1",
                                 @"upTime":@"desc"};

    [manager POST:URL parameters:parameter progress:^(NSProgress * _Nonnull uploadProgress) {

    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

        NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
        for (NSDictionary *rowsDict in dict[@"rows"]) {
            NSLog(@"A___%@",rowsDict[@"title"]);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

    }];
}

request_B、request_C分別為請求第二頁與第三頁數據,這裡不重復書寫。為了顯示更加明顯,在請求中打印了對應新聞的標題內容。

運行打印如下:

任務均完成,刷新界面
C___搞笑,不是認真的
C___攝影 | 街拍
C___生活小竅門
C___傳統中國
C___想吃的美食系列
B___時間的見證者
B___沒事 來吐槽吧……
B___觸動心靈的攝影
B___攝影 | 黑白印記
B___每日插畫推薦
A___左愛情,右面包
A___潮我看
A___世界各國的人們怎麼過情人節
A___一點創意點亮生活
A___攝影 | 隨手拍

運行後馬上接收到了線程組完成的提示,之後數據才依次請求下來,很明顯三個單純的AFNet請求已經不能滿足我們的需求了。線程組完成時並沒有在我們希望的時候給予通知。在真實開發中會造成的問題為多個請求均加載完成,但界面已在未得到數據前提前刷新導致界面空白。

因此對於這種問題需要另辟蹊徑,這裡我們就要借助GCD中的信號量dispatch_semaphore進行實現,即營造線程同步情況。

dispatch_semaphore信號量為基於計數器的一種多線程同步機制。用於解決在多個線程訪問共有資源時候,會因為多線程的特性而引發數據出錯的問題。

如果semaphore計數大於等於1,計數-1,返回,程序繼續運行。如果計數為0,則等待。

dispatch_semaphore_signal(semaphore)為計數+1操作。dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設置等待時間,這裡設置的等待時間是一直等待。我們可以通俗的理解為單櫃台排隊點餐,計數默認為0,每當有顧客點餐,計數+1,點餐結束-1歸零繼續等待下一位顧客。比較類似於NSLock。

我們將網絡請求通用方法進行修改如下:

- (void)request_A {
    //創建信號量並設置計數默認為0
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];;
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    NSDictionary *parameter = @{@"token":@"63104AB32427EBF89B957BBD1A5C5C11",
                                 @"page":@"1",
                                 @"upTime":@"desc"};

    [manager POST:URL parameters:parameter progress:^(NSProgress * _Nonnull uploadProgress) {

    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //計數+1操作
        dispatch_semaphore_signal(sema);
        NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
        for (NSDictionary *rowsDict in dict[@"rows"]) {
            NSLog(@"A___%@",rowsDict[@"title"]);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        ////計數+1操作
        dispatch_semaphore_signal(sema);
    }];
    //若計數為0則一直等待
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}

為方便閱讀,偽代碼如下:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網絡請求:{
        成功:dispatch_semaphore_signal(sema);
        失敗:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

這時我們再運行程序,打印如下:

C___聆聽的耳朵
C___家居 | 你的家裡還缺點什麼?
C___logo設計
C___時裝
C___搞笑,不是認真的
A___左愛情,右面包
A___潮我看
A___世界各國的人們怎麼過情人節
A___一點創意點亮生活
A___攝影 | 隨手拍
B___時間的見證者
B___沒事 來吐槽吧……
B___觸動心靈的攝影
B___攝影 | 黑白印記
B___每日插畫推薦
任務均完成,刷新界面

運行打印可見,通過信號量dispatch_semaphore完美的解決了此問題,並且網絡請求仍為異步,不會堵塞當前主線程。

二、某界面存在多個請求,希望請求依次執行。

對於這個問題通常會通過線程依賴進行解決,因使用GCD設置線程依賴比較繁瑣,這裡通過NSOperationQueue進行實現,這裡采用比較經典的例子,三個任務分別為下載圖片,打水印和上傳圖片,三個任務需異步執行但需要順序性。代碼如下,下載圖片、打水印、上傳圖片仍模擬為分別請求新聞列表3頁數據。

    //1.任務一:下載圖片
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_A];
    }];

    //2.任務二:打水印
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_B];
    }];

    //3.任務三:上傳圖片
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_C];
    }];

    //4.設置依賴
    [operation2 addDependency:operation1];      //任務二依賴任務一
    [operation3 addDependency:operation2];      //任務三依賴任務二

    //5.創建隊列並加入任務
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

首先我們使用未添加信號量dispatch_semaphore時運行,打印如下

B___時間的見證者
B___沒事 來吐槽吧……
B___觸動心靈的攝影
B___攝影 | 黑白印記
B___每日插畫推薦
A___潮我看
A___左愛情,右面包
A___世界各國的人們怎麼過情人節
A___一點創意點亮生活
A___攝影 | 隨手拍
C___盤
C___聆聽的耳朵
C___家居 | 你的家裡還缺點什麼?
C___logo設計
C___時裝

根據打印結果可見,若不對請求方法做處理,其運行結果並不是我們想要的,聯系實際需求,A、B、C請求分別對應下載圖片、打水印、上傳圖片,而此時運行順序則為B->A->C,在未獲得圖片時即執行打水印操作明顯是錯誤的。重復運行亦會出現不同結果,即請求不做處理,其結果不可控無法預測。線程依賴設置並未起到作用。

解決此問題的方法仍可通過信號量dispatch_semaphore進行解決。我們將請求方法替換為添加dispatch_semaphore限制的形式。即

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網絡請求:{
        成功:dispatch_semaphore_signal(sema);
        失敗:dispatch_semaphore_signal(sema);
}]
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

再次重復運行,我們會發現每次運行結果均一致,A、B、C三任務異步順序執行(A->B->C)

A___潮我看
A___左愛情,右面包
A___世界各國的人們怎麼過情人節
A___一點創意點亮生活
A___攝影 | 隨手拍
B___時間的見證者
B___沒事 來吐槽吧……
B___觸動心靈的攝影
B___攝影 | 黑白印記
B___每日插畫推薦
C___盤
C___聆聽的耳朵
C___家居 | 你的家裡還缺點什麼?
C___logo設計
C___時裝

通過重復運行打印結果可證實確實實現了我們想要的效果。這樣即解決了所提出的問題二。

後續

在開發中我們會遇到很多需要進行線程同步或請求同步的情況。比如彈出視圖設置某功能,在點擊確認按鈕時發生請求,在請求成功同時銷毀彈出視圖等,均需要保證在請求真正完成時進行下一步操作。因此把握網絡請求完成的正確時機還是很有必要的。

暫時寫到這裡,若有更好實例會繼續在本文更新。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved