iOS_21團購_通過block對請求工具類的代理方法進行二次封裝 – iPhone手機開發技術文章 iPhone軟體開發教學課程

最終效果圖:

【點評】提供的工具類DPAPI 在請求完畢後,使用的是代理機制,當一次請求成功或者失敗時,會調用代理的相應方法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 : 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 deals,NSError *err);
@interface DealRequestTool ()
{
    // 重要,每一次請求,對應一個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 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

發佈留言