iOS_21團購_通過block對請求工具類的代理方法進行二次封裝
最終:
【點評】提供的工具類DPAPI 在請求完畢後,使用的是代理機制,當一次請求成功或者失敗時,會調用代理的相應方法<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PGJyPgo8L3A+CjxwPs6qwcu9q7XjxsDM4bmptcS5pL7fwOBEUEFQSb340NC2/rTOt+LXsCw8L3A+CjxwPtTZtM62qNLlwcvSu7j2YmxvY2s6PC9wPgo8cD50eXBlZGVmICB2b2lkKF5SZXF1ZXN0RG9uZUNhbGxCYWNrQmxvY2spKGlkIGRlYWxzLE5TRXJyb3IgKmVycik7PGJyPgo8L3A+CjxwPrjDYmxvY2vT0MG9uPayzsr9LDwvcD4KPHA+tdoxuPayzsr9ysc6s8m5psqxLLf+zvHG97e1u9i1xGRlYWxz19a15Mr91+k8L3A+CjxwPrXaMrj2ss7K/crHOsfrx/PKp7DcyrEst/7O8cb3t7W72LXEyqew3NDFz6I8L3A+CjxwPsG9uPayzsr9t9ax8LbU06a0+sDttcTBvbj2t723qCi8tLPJuaahosqnsNzKsbfWsfC199PDtcS0+sDtt723qCk8L3A+CjxwPjxicj4KPC9wPgo8cD48L3A+CjxwIGNsYXNzPQ=="p1">該block調用時機是:
在一次DPAPI請求完成後,無論失敗和成功都要在代理方法中調用的該block,
將該次請求的請求結果進行回傳給工具類,
工具類內部再通過對回傳結果進行判斷,
進而決定要不要調用外部的successBlock或者failBlock
本block是與一個請求對應,並且存入成員字典中儲存,
目的是處理並發請求時,確保一次請求與一個請求結果的回調block一一對應
工具類:
DealRequestTool.h
//// DealRequestTool.h// 帥哥_團購//// Created by beyond on 14-8-19.// Copyright (c) 2014年 com.beyond. All rights reserved.// 單例,使用二次block封裝向伺服器發送請求的所有代碼#import // 定義請求成功後調用的block,將伺服器返回的字典數組轉成對象數組後,回傳typedef void(^successBlock)(NSArray *deals);// 定義請求失敗後調用的block,將伺服器返回的出錯資訊回傳typedef void(^failBlock)(NSError *error);@interface DealRequestTool : NSObjectsingleton_interface(DealRequestTool)// 對象方法,內部封裝了向伺服器提交的參數字典(從工具類擷取),並且通過調用自訂方法,使用二次block封裝了DPAPI的代理方法- (void)dealRequestWithPageNo:(int)pageNo success:(successBlock)successBock fail:(failBlock)failBlock;@end
工具類:
DealRequestTool.m
//// DealRequestTool.m// 帥哥_團購//// Created by beyond on 14-8-19.// Copyright (c) 2014年 com.beyond. All rights reserved.// 單例,使用二次block封裝向伺服器發送請求的所有代碼#import "DealRequestTool.h"#import "MetaDataTool.h"#import "City.h"#import "DPAPI.h"#import "Deal.h"#import "Order.h"// 重要,定義一個 一次DPAPI請求完成後,無論失敗和成功都會在代理方法中調用的block,本block與一個請求對應,存入成員字典中儲存,目的是處理並發時,確保一次請求與一個請求結果的回調block一一對應typedef void(^RequestDoneCallBackBlock)(id deals,NSError *err);@interface DealRequestTool (){ // 重要,每一次請求,對應一個RequestDoneCallBackBlock,並且在DPAPI的代理方法裡面,在請求結束後,調用RequestDoneCallBackBlock回傳伺服器的成功或失敗資訊,因只需初始化一次,在Init方法 NSMutableDictionary *_requestBlockDict;}@end@implementation DealRequestToolsingleton_implementation(DealRequestTool)- (id)init{ if (self = [super init]) { // 重要,每一次請求,對應一個requestBlock,並且在代理方法裡面調用requestBlock回傳伺服器的成功或失敗資訊,只需初始化一次,在Init方法 _requestBlockDict = [NSMutableDictionary dictionary]; } return self;}// 1.對象方法,供外部調用.內部封裝了向伺服器提交的參數字典(從工具類擷取),並且通過調用自訂方法,使用二次block封裝了DPAPI的代理方法- (void)dealRequestWithPageNo:(int)pageNo success:(successBlock)successBock fail:(failBlock)failBlock{ NSMutableDictionary *paramsDict = [NSMutableDictionary dictionary]; // 1.從工具類中擷取所有的請求參數,即當前城市、商區、排序等 [paramsDict addEntriesFromDictionary:[self getAllRequestParamsDict]]; // 1.1.添加頁碼參數(int轉成string) [paramsDict setObject:@(pageNo) forKey:@"page"]; // 2.重要~~~~調用自訂方法,發送DPAPI請求 [self requestWithUrl:@"v1/deal/find_deals" params:paramsDict requestBlock:^(id deals, NSError *err) { // 這兒,就可以拿到與本次請求相對應的回調block,參數裡已經包含了本次請求的成功字典數組 和 失敗資訊 // 現在只需判斷,請求的回調block 有沒有成功的返回結果,如果有,並且外界調用者需要結果 ,才把相應的請求結果 再次回調給外界 if (deals) { if (successBock) { NSMutableArray *dealsArr = [NSMutableArray array]; // 從返回結果根據Key,取出所有的字典數組,一一遍曆,轉成模型 NSArray *arr = deals[@"deals"]; for (NSDictionary *dict in arr) { Deal *deal = [[Deal alloc]init]; [deal setValuesWithDict:dict]; [dealsArr addObject:deal]; } // 將封裝好的模型數組回調給外界調用者(格子顯示資料) successBock(dealsArr); } } else { // 同樣,請求的回調block 有沒有失敗的資訊,如果有,並且外界調用者需要結果 ,才把相應的請求結果 再次回調給外界 if (failBlock) { failBlock(err); } } }]; }// 自訂方法,從工具類撮所有的請求參數- (NSDictionary *)getAllRequestParamsDict{ NSMutableDictionary *params = [NSMutableDictionary dictionary]; // 1.1.添加城市參數 NSString *city = [MetaDataTool sharedMetaDataTool].currentCity.name; [params setObject:city forKey:@"city"]; // 1.2.添加地區參數 NSString *district = [MetaDataTool sharedMetaDataTool].currentDistrictName; if (district && ![district isEqualToString:kAllDistrict]) { [params setObject:district forKey:@"region"]; } // 1.3.添加分類參數 NSString *category = [MetaDataTool sharedMetaDataTool].currentCategoryName; if (category && ![category isEqualToString:kAllCategory]) { [params setObject:category forKey:@"category"]; } // 1.4.添加排序參數 Order *order = [MetaDataTool sharedMetaDataTool].currentOrder; if (order) { // 按照其他方式排序 [params setObject:@(order.index) forKey:@"sort"]; } return params;}// 2.調用自訂方法,發送DPAPI請求,並且DPAPI請求結束後,會在代理方法中調用本次請求對應的requestDoneCallBackBlock- (void)requestWithUrl:(NSString *)url params:params requestBlock:(RequestDoneCallBackBlock)callBackBlock{ DPAPI *api = [DPAPI sharedDPAPI]; // 重要~~~必須返回本次請求的DPRequest,並且與requestBlock一一對應,存入字典裡面,因為可能出現並發請求的情況,如果不將回調requestBlock與DPRequest一一對應起來,就會出現下一次請求的結果 覆蓋上一次請求的情況發生 DPRequest *dpRequest= [api requestWithURL:url params:params delegate:self]; // 重要~~~用成員變數_requestBlockDict記住本次的請求,和與之對應的回調requestBlock,在代理方法就可以從字典中取出,設定回調block的參數(_deals或者錯誤資訊) [_requestBlockDict setObject:callBackBlock forKey:dpRequest.description];}#pragma mark - 代理方法// 一次請求成功時調用,參數:該次請求的請求對象,該次請求的請求結果- (void)request:(DPRequest *)request didFinishLoadingWithResult:(id)result{ // 先從成員字典中,根據本次的請求對象,取出本次請求的回調block RequestDoneCallBackBlock callBackBlock = [_requestBlockDict objectForKey:request.description]; // 直接回調與本次請求對應的block,並將請求結果(deals字典數組)回傳,因本代理方法是請求成功時調用,故回調block的失敗參數不填寫 callBackBlock(result,nil);}// 一次請求失敗時調用,參數:該次請求的請求對象,該次請求的失敗原因- (void)request:(DPRequest *)request didFailWithError:(NSError *)error{ // 先從成員字典中,根據本次的請求對象,取出本次請求的回調block RequestDoneCallBackBlock callBackBlock = [_requestBlockDict objectForKey:request.description]; // 直接回調與本次請求對應的block,並將失敗原因回傳,因本代理方法是請求失敗時調用,故回調block的成功參數不填寫 callBackBlock(nil,error);}@end
外部調用者:即控制器
DealListController.m
//// DealListController.m// 帥哥_團購//// Created by beyond on 14-8-14.// Copyright (c) 2014年 com.beyond. All rights reserved.// 點擊dock上面的【團購】按鈕對應的控制器,上面是導覽列,導覽列右邊是searchBar,導覽列左邊是一個大按鈕(TopMenu)(內部由三個小按鈕組成)#import "DealListController.h"// 導覽列左邊是一個大按鈕(頂部菜單)#import "TopMenu.h"// 封裝的自訂cell#import "DealCell.h"// 點評提供的封裝發送請求的類#import "DPAPI.h"// 工具類#import "MetaDataTool.h"// 封裝請求的工具類#import "DealRequestTool.h"// 模型類#import "City.h"#import "Deal.h"#define kItemW 250#define kItemH 250@interface DealListController(){ // 用於接收伺服器返回的字典數組----轉化成的對象數組,供格子們顯示 NSMutableArray *_deals;}@end@implementation DealListController// 覆蓋控制器的init方法- (id)init{ // 建立一個流布局 UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; // 設定流布局裡面的每一個格子寬和高,即每一個網格的尺寸 layout.itemSize = CGSizeMake(kItemW, kItemH); // 調用父類UICollectionViewController的initWithCollectionViewLayout方法,(self這兒找不到,會到父類裡去找方法) return [self initWithCollectionViewLayout:layout];}- (void)viewDidLoad{ [super viewDidLoad]; _deals = [NSMutableArray array]; // 0.監聽所有改變的通知(如城市、商區、分類、排序) kAddAllNotes(dataChanged) // 1.頂部導覽列的基本設定 [self setNavigationBar]; // 2.collectionView的基本設定 [self setCollectionView];}// 0.監聽到城市等更改時,向伺服器發出請求- (void)dataChanged{ // 重要~~~~~調用封裝好請求工具類,發送請求,參數:頁碼數, [[DealRequestTool sharedDealRequestTool]dealRequestWithPageNo:1 success:^(NSArray *deals) { // 先移除舊的資料 [_deals removeAllObjects]; // 再將封裝好的對象數組加到成員變數 [_deals addObjectsFromArray:deals]; // 接下來就可以給collectionView提供資料來源了 [self.collectionView reloadData]; } fail:^(NSError *error) { log(@"request---fail:%@",error); }];}// 2.頂部導覽列的基本設定- (void)setNavigationBar{ // 1.右邊的搜尋方塊 UISearchBar *s = [[UISearchBar alloc] init]; s.frame = CGRectMake(0, 0, 210, 35); s.placeholder = @"請輸入商品名、地址等"; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:s]; // 2.左邊的功能表列 TopMenu *top = [[TopMenu alloc] init]; // 重要,TopMenu裡面的item點擊後,建立的PopMenu將要添加到哪兒去???就是本控制器的view top.controllerView = self.view; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:top]; }// 3.collectionView的基本設定- (void)setCollectionView{ // 1.設定collectionView的背景色,(不像tableViewController,本控制器的view是UIView,在UIView裡面再添加的collectionView) self.collectionView.backgroundColor = kGlobalBg; // 2.註冊cell要用到的xib [self.collectionView registerNib:[UINib nibWithNibName:@"MyDealCell" bundle:nil] forCellWithReuseIdentifier:@"DealCell"]; // 3.設定collectionView永遠支援垂直滾動,為下拉重新整理準備(彈簧) self.collectionView.alwaysBounceVertical = YES; }// 4.重要~~~因為在控制器建立時,寬預設是768,高預設是1024,不管橫豎屏// 只有在viewWillAppear和viewDidAppear方法中,可以取得view最準確的(即實際的)寬和高(width和height)- (void)viewWillAppear:(BOOL)animated{ // 預設計算layout [self didRotateFromInterfaceOrientation:0];}#pragma mark - 父類方法// 攔截,螢幕即將旋轉的時候調用(控制器監控旋轉螢幕)- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{ log(@"螢幕即將旋轉");}// 攔截,旋轉螢幕完畢的時候調用- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{ // 1.取出建立CollectionViewController時傳入的的UICollectionViewFlowLayout UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout; // 2.計算間距 CGFloat v = 0; CGFloat h = 0; CGFloat height = self.view.frame.size.height -44; CGFloat width = self.view.frame.size.width; if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation) ) { // 橫屏的間距 v = (height - 2 * kItemH) / 3; h = (width - 3 * kItemW) / 4; } else { // 豎屏的間距 v = (height - 3 * kItemH) / 4; h = (width - 2 * kItemW) / 3; } // 3.動畫調整格子之間的距離 [UIView animateWithDuration:4.0 animations:^{ // 上 左 下 右 四個方向的margin layout.sectionInset = UIEdgeInsetsMake(h, h, v, h); // 每一行之間的間距 layout.minimumLineSpacing = h; }];}#pragma mark - collectionView代理方法// 共有多少個Item(就是格子Cube)- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ return _deals.count;}// 產生每一個獨一無二的格子- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ static NSString *cellID = @"DealCell"; DealCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath]; // 設定獨一無二的資料 cell.deal = _deals[indexPath.row]; // 返回cell return cell;} @end