在實際開發中我們通常會遇到這樣一種需求:某個頁面加載時通過網絡請求獲得相應的數據,再做某些操作。有時候加載的內容需要通過好幾個請求的數據組合而成,比如有兩個請求A和B,我們通常為了省事,會將B請求放在A請求成功的回調中發起,在B的成功回調中將數據組合起來,這樣做有明顯的問題:
1.請求如果多了,需要寫許多嵌套的請求
2.如果在除了最后一個請求前的某個請求失敗了,就不會執行后面的請求,數據無法加載
3.請求變成同步的,這是最大的問題,在網絡差的情況下,如果有n個請求,意味着用戶要等待n倍於並發請求的時間才能看到內容
同步請求這么low的方式當然是不可接受的,所以我們要並發這些請求,在所有請求都執行完成功回調后,再做加載內容或其他操作,考慮再三,選擇用GCD的dispatch_group。
dispatch_group通常有兩種用法,一種是
dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
創建一個dispatch_group_t, 將並發的操作放在block中,在
dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
的block中執行多組block執行完畢后的操作,對於網絡請求來說,在請求發出時他就算執行完畢了,並不會等待回調,所以不滿足我們的需求。
所以采用另一種用法:
使用dispatch_group_enter和dispatch_group_leave,這種方式使用更為靈活,enter和leave必須配合使用,有幾次enter就要有幾次leave,否則group會一直存在。當所有enter的block都leave后,會執行dispatch_group_notify的block。
我們當然可以在網絡請求前enter,在執行完每個請求的成功回調后leave,再在notify中執行內容加載,這樣看來問題就解決了,但還是有點小小不爽,每次發起組請求我都得創建group,寫一堆的enter和leave,既麻煩也不利於復用,很自然我們想到把他封裝一下,最好能做到將一個網絡請求加到組里,而不用修改原先的網絡請求代碼,就像這樣:
[[MDXuexiBaoAPI sharedInstance] postForGroup:^{ request1 success:^(id responseObject) { } failure:^(NSError *error) { }]; request2 success:^(id responseObject) { } failure:^(NSError *error) { }]; } success:^{ // group success } failure:^(NSArray *errorArray) { // group failure }]
如果我想做到這種效果,肯定要到網絡單例層去做些修改,但我又不想改變現有的底層請求方法,所以我采用了
method_exchangeImplementations(<#Method m1#>, <#Method m2#>) 這個函數,基於現有的底層請求方法,實現一套組的請求方法,替換掉原先的方法,在組請求都發送完畢后,再換回原先的方法。
但這里有一些可怕的坑要處理,因為使用方法替換是很危險的。
1.我做了替換后,正常的非組網絡請求也會走替換后的方法,但我不需要他走替換后的方法。
2.假如我同時發起了多個組請求,組和組之間要如何區分,不同的組是不應該相互影響的。
一開始我考慮給請求一個mark,標記他是屬於哪個group的,但這需要你已經把請求封裝成了一個對象,如果你的項目和我的一樣,只是執行一個方法,是不好給他加標記的。
在一陣頭腦風暴后,我決定用隊列來區分每個gorup。
具體做法就是創建每個group時,開啟一個隊列,給隊列動態添加group屬性,一個隊列對應一個group。在隊列中替換方法,發起組里的請求,再替換回原先的方法,這樣在replaced method里只需要拿到當前的隊列,就可以拿到group,如果group是nil,說明是正常的非組請求,執行original method;如果group不是nil,根據group來enter和leave,每個group也能區分開。
創建group時,給group添加了一個errorArray,用來記錄組里請求的error,只要errorArray不為空,就會走組失敗的block。
下面上代碼:
typedef void(^BlockAction)(); typedef void(^GroupResponseFailure)(NSArray * errorArray); static char groupErrorKey; static char queueGroupKey;
- (void)sendPOSTRequestInGroup:(NSString *)strURL withData:(NSDictionary *)data paramForm:(ParamForm)paramForm withTimeout:(NSTimeInterval)timeout showAlert:(BOOL)show success:(BlockResponse)success failure:(BlockResponseFailure)failure { dispatch_group_t group = objc_getAssociatedObject([NSOperationQueue currentQueue], &queueGroupKey); // 如果是非組請求 if (group == nil) { // 執行original method [self sendPOSTRequestInGroup:strURL withData:data paramForm:paramForm withTimeout:timeout showAlert:show success:success failure:failure]; return; } dispatch_group_enter(group); // 執行original method [self sendPOSTRequestInGroup:strURL withData:data paramForm:paramForm withTimeout:timeout showAlert:show success:^(id responseObject) { success(responseObject); dispatch_group_leave(group); } failure:^(NSError *error) { NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey); [arrayM addObject:error]; failure(error); dispatch_group_leave(group); }]; } - (void)sendGroupPostRequest:(BlockAction)requests success:(BlockAction)success failure:(GroupResponseFailure)failure { if (requests == nil) { return; } dispatch_group_t group = dispatch_group_create(); objc_setAssociatedObject(group, &groupErrorKey, [NSMutableArray array], OBJC_ASSOCIATION_RETAIN_NONATOMIC); Method originalPost = class_getInstanceMethod(self.class, @selector(sendPOSTRequest:withData:paramForm:withTimeout:showAlert:success:failure:)); Method groupPost = class_getInstanceMethod(self.class, @selector(sendPOSTRequestInGroup:withData:paramForm:withTimeout:showAlert:success:failure:)); NSOperationQueue *queue = [[NSOperationQueue alloc] init]; objc_setAssociatedObject(queue, &queueGroupKey, group, OBJC_ASSOCIATION_RETAIN_NONATOMIC); queue.qualityOfService = NSQualityOfServiceUserInitiated; queue.maxConcurrentOperationCount = 3; [queue addOperationWithBlock:^{ method_exchangeImplementations(originalPost, groupPost); requests(); // 發出請求后就可以替換回original method,不必等待回調,盡量減小替換的時間窗口 method_exchangeImplementations(originalPost, groupPost); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey); if (arrayM.count > 0) { if (failure) { failure(arrayM.copy); } } else if(success) { success(); } }); }]; }
- (void)sendPOSTRequestInGroup:(NSString *)strURL withData:(NSDictionary *)data paramForm:(ParamForm)paramForm withTimeout:(NSTimeInterval)timeout showAlert:(BOOL)show success:(BlockResponse)success failure:(BlockResponseFailure)failure
替換網絡工具層的底層post請求
sendPOSTRequest:withData:paramForm:withTimeout:showAlert:success:failure:
這樣在
- (void)sendGroupPostRequest:(BlockAction)requests success:(BlockAction)success failure:(GroupResponseFailure)failure
requests block中,把網絡請求扔進去,單個請求本身的success和failure都能執行,success執行組成功的代碼,failure中可以拿到每個請求的error,作相應處理。