為什麼說,你純看代碼而沒有碰到這個場景你就算看懂了也沒法理解?
我碰到了一個什麼問題?
來說一個場景,打開你的Instagram,如果手機有VPN,請連接上VPN.打開搜索頁面,連續輸入m,i,k,e.
你會發現,小菊花轉了4次.可以想象,客戶端向服務端發起了4個請求,搜索的字段分別是"m","mi","mik","mike".
那麼問題來了.這四次搜索肯定是並發的,也就是說,客戶端同時向服務端發起了四次搜索請求,那麼,怎麼做到每次返回的結果總是最後一次輸入的結果呢?
來看下面這段代碼. for (int i = 1; i <= 10; i++) { [Seller requestSellerWithCompletion:^(id object) { NSLog(@"finished download %d",i); }]; }
這個requestSellerWithCompletion方法就是我封裝了最簡單的一個AFNetworkingOperation請求.
內容如下.
+ (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的值不就行了麼.
我是這麼改到.
+ (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循環裡這麼做.
for (int i = 1; i <= 10; i++) { if (operation) { [operation cancel]; } operation = [Seller requestSellerWithCompletion:^(id object) { NSLog(@"finished download %d",i); }]; }
運行一下看看console .
正確了.
學而不思則罔,思而不學則殆.
每個人要了解自己的優缺點.
為什麼這麼說呢?
像我寫代碼就比較喜歡實現,而不是探究底層.
以前我一直覺得寫出特別精美的界面和酷炫的交互是一件特別屌的事情,其實這些東西在你基本功修煉的特別扎實對一些系統底層的實現特別了解的時候都是很自然而然就掌握的事情,而把大量時間花在這上面並不劃算.你做的交互再屌,不還是用的CoreAnimation麼.
所以我們應該把精力和時間投入在更值得學習的東西上.
共勉.