最终效果图:
【点评】提供的工具类DPAPI 在请求完毕后,使用的是代理机制,当一次请求成功或者失败时,会调用代理的相应方法
为了将点评提供的工具类DPAPI进行二次封装,
再次定义了一个block:
typedef void(^RequestDoneCallBackBlock)(id result,NSError *err);
该block有两个参数,
第1个参数是:成功时,服务器返回的result结果字典
第2个参数是:请求失败时,服务器返回的失败信息
两个参数分别对应代理的两个方法(即成功、失败时分别调用的代理方法)
该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 <Foundation/Foundation.h> // 定义请求成功后调用的block,将服务器返回的字典数组转成对象数组后,回传 typedef void(^SuccessBlock)(NSArray *deals); // 定义请求失败后调用的block,将服务器返回的出错信息回传 typedef void(^FailBlock)(NSError *error); @interface DealRequestTool : NSObject singleton_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 result,NSError *err); @interface DealRequestTool ()<DPRequestDelegate> { // 重要,每一次请求,对应一个RequestDoneCallBackBlock,并且在DPAPI的代理方法里面,在请求结束后,调用RequestDoneCallBackBlock回传服务器的成功或失败信息,因只需初始化一次,在Init方法 NSMutableDictionary *_requestBlockDict; } @end @implementation DealRequestTool singleton_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 result, NSError *err) { // 这儿,就可以拿到与本次请求相对应的回调block,参数里已经包含了本次请求的成功字典数组 和 失败信息 // 现在只需判断,请求的回调block 有没有成功的返回结果,如果有,并且外界调用者需要结果 ,才把相应的请求结果 再次回调给外界 if (deals) { if (successBock) { NSMutableArray *dealsArr = [NSMutableArray array]; // 从返回结果根据Key,取出所有的字典数组,一一遍历,转成模型 NSArray *arr = result[@"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)(内部由三个小按钮组成<TopMenuItem>) #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()<DPRequestDelegate> { // 用于接收服务器返回的字典数组----转化成的对象数组,供格子们显示 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