我碰到了一個什麼問題?
來說一個場景,開啟你的Instagram,如果手機有VPN,請連線上VPN.開啟搜尋頁面,連續輸入m,i,k,e.
你會發現,小菊花轉了4次.可以想象,客戶端向服務端發起了4個請求,搜尋的欄位分別是”m”,”mi”,”mik”,”mike”.
那麼問題來了.這四次搜尋肯定是併發的,也就是說,客戶端同時向服務端發起了四次搜尋請求,那麼,怎麼做到每次返回的結果總是最後一次輸入的結果呢?
來看下面這段程式碼.
1 2 3 4 5 6 7 8 |
for (int i = 1; i <= 10; i++) { [Seller requestSellerWithCompletion:^(id object) { NSLog(@"finished download %d",i); }]; } |
這個requestSellerWithCompletion方法就是我封裝了最簡單的一個AFNetworkingOperation請求.
內容如下.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
+ (void)requestSellerWithCompletion:(requestFinishedCompletionBlock)successBlock { AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [manager GET:kRequestSellerURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { NSArray *sellerArray = [MTLJSONAdapter modelsOfClass:[Seller class] fromJSONArray:responseObject[@"data"] error:nil]; if (successBlock) { // NSLog(@"current operation count is %d",[manager.operationQueue operations].count); successBlock(sellerArray); } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { }]; } |
好,我們執行以下.console打出的結果如下.
對多執行緒稍有了解的都會知道這結果很正常.併發嗎.試想一下,如果你在UITextField的delegate方法裡直接對搜尋欄位執行搜尋欄位,那麼服務端返回的結果肯定是有問題的,因為很有可能你輸入的是mike,而返回的是”mi”的搜尋結果.
所以,在上面那個for迴圈中,我們只需要最後一次執行結果.也就是i = 10的執行結果.前9次不管你是執行了也好,還是我中斷了也好,我是不需要的.
那麼怎麼實現呢?
首先,需要否決的是想通過設定maxConcurrentOperationCount = 1解決問題的方案.
原因很簡單,request的順序執行並不能保證response返回也是順序的.因為網速是時快時慢得.
而且maxConcurrentOperationCount這個引數實際上並不是幹這個的.
他的適合的使用場景是為了它的主要意義就是控制連線數,2G網路下一次只能維持一個連結,3G是2個,
4G和wifi是不限。這個是對應協議的限制。如果超過這個限制發出的請求,就會報超時。
所以你以後封裝httpClient的時候可能需要依據網路條件來設定你OperationQueue的這個引數值.
我們應該怎麼做?
我又想到了另外一個辦法,每次執行request的時候先清空OperationQueue裡的所有operation.
也即呼叫[operationQueue cancelAllOperations]
其實這種想法暴露了我的iOS開發功底不紮實的問題.
來看看蘋果文件對這個方法的描述.
Canceling the operations does not automatically remove them from the queue or stop those that are currently executing.
就是即便呼叫了這個方法也並不能移除正在執行的operation.所以,翻譯過來就是,然並卵.
這條路已經死了.
到底應該怎麼辦?
首先NSOperation有一個Bool值,叫做cancel.這個Bool值並不能控制operation的執行和終止,他只是起一個標記作用.
Yes的時候就是說,我雖然在執行,但是我被cancel掉了.
那麼我們每次執行request的時候都把上一個operationcancel掉,然後在completionBlock中判斷operation是否cancel,如果cancel那麼不返回response的值不就行了麼.
我是這麼改到.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
+ (AFHTTPRequestOperation *)requestSellerWithCompletion:(requestFinishedCompletionBlock)successBlock { AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; AFHTTPRequestOperation * operation = [manager GET:kRequestSellerURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { if (operation.isCancelled) { NSLog(@"operation is Canceled"); }else{ NSArray *sellerArray = [MTLJSONAdapter modelsOfClass:[Seller class] fromJSONArray:responseObject[@"data"] error:nil]; if (successBlock) { // NSLog(@"current operation count is %d",[manager.operationQueue operations].count); successBlock(sellerArray); } } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { }]; return operation; } |
然後在for迴圈裡這麼做.
1 2 3 4 5 6 7 8 9 10 11 |
for (int i = 1; i <= 10; i++) { if (operation) { [operation cancel]; } operation = [Seller requestSellerWithCompletion:^(id object) { NSLog(@"finished download %d",i); }]; } |
執行一下看看console .
正確了.
學而不思則罔,思而不學則殆.
每個人要了解自己的優缺點.
為什麼這麼說呢?
像我寫程式碼就比較喜歡實現,而不是探究底層.
以前我一直覺得寫出特別精美的介面和酷炫的互動是一件特別屌的事情,其實這些東西在你基本功修煉的特別紮實對一些系統底層的實現特別瞭解的時候都是很自然而然就掌握的事情,而把大量時間花在這上面並不划算.你做的互動再屌,不還是用的CoreAnimation麼.
所以我們應該把精力和時間投入在更值得學習的東西上.
共勉.