iOS-AFNetworking網(wǎng)絡(luò)層封裝設(shè)計方案

前言

網(wǎng)絡(luò)層在項目開發(fā)中是不可缺少的一部分,網(wǎng)絡(luò)層在一個App中承載了API調(diào)用,用戶操作日志記錄等任務(wù)。雖然蘋果對網(wǎng)絡(luò)請求部分已經(jīng)做了很好的封裝,但業(yè)界內(nèi)最受歡迎的還是第三方庫AFNetworking,很多工程師針對自己的項目對此做了各式各樣的封裝,我看了很多,每次接手項目的時候最關(guān)注的也是這一塊??吹礁魑患軜?gòu)師各顯神通展示了各種技巧,我為之感到興奮,但興奮之余,往往因為一些缺陷而感到失望。下面給大家介紹一下我目前使用的框架。

在介紹之前,在此說明:本人技術(shù)有限,大家各持所需,哪怕有一個人看了之后有一點點收獲,說明我做的事情就是有意義的,當(dāng)然,哪里不好,有說錯的地方,期望各位大神留言指正。

一、封裝的類

封裝的類

先說說BaseAPIRequest這個類:
BaseAPIRequest.h

#import <Foundation/Foundation.h>
#import "BaseAPIProxy.h"

/** 首先是定義的四個協(xié)議 */
@class BaseAPIRequest;
/*---------------------API回調(diào)-----------------------*/
@protocol APIManagerApiCallBackDelegate <NSObject>
@required
- (void)managerCallAPIDidSuccess:(BaseAPIRequest *)request;
- (void)managerCallAPIDidFailed:(BaseAPIRequest *)request;
@end

/*---------------------API參數(shù)-----------------------*/
@protocol APIManagerParamSourceDelegate <NSObject>
@required
- (NSDictionary *)paramsForApi:(BaseAPIRequest *)request;
@optional
- (void (^)(id <AFMultipartFormData> formData))uploadBlock:(BaseAPIRequest *)request;
@end

/**  以上兩個協(xié)議部分,正如注釋所示:三個代理方法,是每個界面必須要遵守寫出來的。
      (1)需要上傳的參數(shù);
      (2)請求成功、失敗之后的回調(diào)處理。
   (除非這個界面是分頁請求,只需要寫一個API參數(shù)的協(xié)議,這個后面再說。)
*/

/**
 在此插一點說明:
 在iOS中有很多對象間數(shù)據(jù)的傳遞方式,大多數(shù)App在網(wǎng)絡(luò)層所采用的方案主要集中于這三種:Delegate,Notification,Block。
 在這里,我主要以Delegate為主,Notification為輔。原因如下:
      (1)盡可能減少跨層數(shù)據(jù)交流的可能,限制耦合
   "跨層數(shù)據(jù)交流:【就是某一層(或模塊)跟另外的與之沒有直接對接關(guān)系的層(或模塊)產(chǎn)生了數(shù)據(jù)交換。】"
      (2)統(tǒng)一回調(diào)方法,便于調(diào)試和維護
      (3)在跟業(yè)務(wù)層對接的部分只采用一種對接手段(delegate)限制靈活性,以此來交換應(yīng)用的可維護性
 */
/*---------------------API驗證器-----------------------*/
//驗證器,用于驗證API的返回或者調(diào)用API的參數(shù)是否正確
/*
 使用場景:
 當(dāng)我們確認(rèn)一個api是否真正調(diào)用成功時,要看的不光是status,還有具體的數(shù)據(jù)內(nèi)容是否為空。由于每個api中的內(nèi)容對應(yīng)的key都不一定一樣,甚至于其數(shù)據(jù)結(jié)構(gòu)也不一定一樣,因此對每一個api的返回數(shù)據(jù)做判斷是必要的,但又是難以組織的。
 為了解決這個問題,manager有一個自己的validator來做這些事情,一般情況下,manager的validator可以就是manager自身。
 
 1.有的時候可能多個api返回的數(shù)據(jù)內(nèi)容的格式是一樣的,那么他們就可以共用一個validator。
 2.有的時候api有修改,并導(dǎo)致了返回數(shù)據(jù)的改變。在以前要針對這個改變的數(shù)據(jù)來做驗證,是需要在每一個接收api回調(diào)的地方都修改一下的。但是現(xiàn)在就可以只要在一個地方修改判斷邏輯就可以了。
 3.有一種情況是manager調(diào)用api時使用的參數(shù)不一定是明文傳遞的,有可能是從某個變量或者跨越了好多層的對象中來獲得參數(shù),那么在調(diào)用api的最后一關(guān)會有一個參數(shù)驗證,當(dāng)參數(shù)不對時不訪問api,同時自身的errorType將會變?yōu)镃TAPIManagerErrorTypeParamsError。這個機制可以優(yōu)化我們的app。
 4.特殊場景:如果用戶會被要求填很多參數(shù),這些參數(shù)都有一定的規(guī)則,比如郵箱地址或是手機號碼等等,我們可以在validator里判斷郵箱或者電話是否符合規(guī)則,比如描述是否超過十個字。從而manager在調(diào)用API之前可以驗證這些參數(shù),通過manager的回調(diào)函數(shù)告知上層controller。避免無效的API請求。加快響應(yīng)速度,也可以多個manager共用.
 */
@protocol APIManagerValidator <NSObject>
@required
/*
 所有的callback數(shù)據(jù)都應(yīng)該在這個函數(shù)里面進行檢查,事實上,到了回調(diào)delegate的函數(shù)里面是不需要再額外驗證返回數(shù)據(jù)是否為空的。
 因為判斷邏輯都在這里做掉了。
 而且本來判斷返回數(shù)據(jù)是否正確的邏輯就應(yīng)該交給manager去做,不要放到回調(diào)到controller的delegate方法里面去做。
 */
- (BOOL)manager:(BaseAPIRequest *)request isCorrectWithCallBackData:(BaseResponse *)data;
@end

/*---------------------APIManager-----------------------*/

@protocol APIManager <NSObject>
@optional
- (Class)responseClass;
- (AFHTTPRequestSerializer <AFURLRequestSerialization> *)requestSerializer;
- (AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer;
- (NSDictionary *)reformParamsForApi:(NSDictionary *)params;
- (void)reformData;
@required

/** 又來required,因為此設(shè)計為網(wǎng)絡(luò)層的抽離,
就是說把所有請求都抽離出來,
在Controller里面只有上傳的參數(shù)以及成功、失敗回到的三個協(xié)議方法,
這樣一來,大大縮小了控制器里面的代碼,
處理業(yè)務(wù)邏輯變得更加清晰,維護起來也更加方便。
 */
/** 請求的url */
- (NSString *)requestPath;
/** 請求類型,(GET、POST...)*/
- (APIManagerRequestType)requestType;

@end

BaseAPIRequest類的聲明

@interface BaseAPIRequest : NSObject
@property (nonatomic, weak) id<APIManagerApiCallBackDelegate> delegate;
@property (nonatomic, weak) id<APIManagerParamSourceDelegate> paramSource;
@property (nonatomic, weak) id<APIManagerValidator> validator;
@property (nonatomic, weak) id<APIManager> child;

/**  不做解釋,看名稱自己理解即可。*/
@property (nonatomic, assign, readonly) BOOL isReachable;
@property (nonatomic, strong) BaseResponse *responseData;
@property (nonatomic, assign, readonly)APIManagerErrorType errorType;
@property (nonatomic, copy) NSString *msg;
@property (nonatomic, assign) BOOL disableErrorTip;

- (instancetype)initWithDelegate:(id)delegate paramSource:(id)paramSource;

/** 此處定義就是每次發(fā)起請求需要調(diào)用的方法 */
- (NSInteger)loadDataWithHUDOnView:(UIView *)view;
- (NSInteger)loadDataWithHUDOnView:(UIView *)view HUDMsg:(NSString *)HUDMsg;

/** 取消所有請求,根據(jù)項目需要使用*/
- (void)cancelAllRequests;
- (void)cancelRequestWithRequestId:(NSInteger)requestID;
@end

BaseAPIRequest.m

#import "BaseAPIRequest.h"
#import "BaseAPIProxy.h"
#import "LoginRequest.h"

@interface BaseAPIRequest ()
@property (nonatomic, copy, readwrite) NSString *errorMessage;
@property (nonatomic, copy, readwrite) NSString *successMessage;
@property (nonatomic, readwrite) APIManagerErrorType errorType;
@property (nonatomic, strong) NSMutableArray *requestIdList;
@property (nonatomic, strong)UIView *hudSuperView;
@property (nonatomic, assign)NSInteger reloginCount;
@end

@implementation BaseAPIRequest

/** 此處就是發(fā)起請求的調(diào)用方法,后文會舉例說明 */
#pragma mark - calling api
- (NSInteger)loadDataWithHUDOnView:(UIView *)view {
    return [self loadDataWithHUDOnView:view HUDMsg:@""];
}

- (NSInteger)loadDataWithHUDOnView:(UIView *)view HUDMsg:(NSString *)HUDMsg {
    [self cancelAllRequests];
    if (view) {
        self.hudSuperView = view;
        [MBProgressHUD showLoadingHUD:HUDMsg onView:self.hudSuperView];
    }
    NSDictionary *params = [self.paramSource paramsForApi:self];
    if ([self.child respondsToSelector:@selector(reformParamsForApi:)]) {
        params = [self.child reformParamsForApi:params];
    }
    /**  
        由于考慮接口的安全性,此處為對參數(shù)加密處理,
        有則根據(jù)需求自行處理,如果接口沒有特殊要求忽略即可。
    */
//    params = [self signatureParams:params];
    NSInteger requestId = [self loadDataWithParams:params];
    return requestId;
}

- (NSInteger)loadDataWithParams:(NSDictionary *)params {
    NSInteger requestId = 0;
    if ([self isReachable]) {
        if ([self.child respondsToSelector:@selector(requestSerializer)]) {
            [BaseAPIProxy sharedInstance].requestSerializer = self.child.requestSerializer;
        } else {

/** 關(guān)于此處:
    AFJSONRequestSerializer:接口已JSON的格式上傳;
    AFHTTPRequestSerializer: 正常的參數(shù)拼接:“name/yuxuan (各種斜杠拼接的)
    根據(jù)自己需求,自行切換模式”
 */
            [BaseAPIProxy sharedInstance].requestSerializer = [AFJSONRequestSerializer serializer]; 
        }
        if ([self.child respondsToSelector:@selector(responseSerializer)]) {
            [BaseAPIProxy sharedInstance].responseSerializer = self.child.responseSerializer;
        } else {
            [BaseAPIProxy sharedInstance].responseSerializer = [AFJSONResponseSerializer serializer];
        }
/** 
    此處就是load請求之后,調(diào)用BaseAPIProxy這個類,可以理解為對AF的封裝(其中可以看到幾個參數(shù):
    * requestType:請求方式:GET、POST...
    * requestPath:url
  (看明白的同學(xué)應(yīng)該想到,前面.h中有兩個需要遵守的協(xié)議:)
      /** 請求的url */
      - (NSString *)requestPath;
      /** 請求類型,(GET、POST...)*/
      - (APIManagerRequestType)requestType;
   就是將參數(shù)拼接到請求當(dāng)中。
*/
        [[BaseAPIProxy sharedInstance] callAPIWithRequestType:self.child.requestType params:params requestPath:self.child.requestPath uploadBlock:[self.paramSource respondsToSelector:@selector(uploadBlock:)]?[self.paramSource uploadBlock:self]:nil success:^(BaseAPIResponse *response) {
            [self successedOnCallingAPI:response];
        } fail:^(BaseAPIResponse *response) {
            [self failedOnCallingAPI:response withErrorType:response.errorType];
        }];
        [self.requestIdList addObject:@(requestId)];
        return requestId;
        
    } else {
        [self failedOnCallingAPI:nil withErrorType:APIManagerErrorTypeNoNetWork];
        return requestId;
    }
    return requestId;
}

請求成功、失敗

- (void)successedOnCallingAPI:(BaseAPIResponse *)response {
    if (self.hudSuperView) {
        [MBProgressHUD hideLoadingHUD];
    }
    [self removeRequestIdWithRequestID:response.requestId];
    
    DLog(@"%@:%@", [self.child requestPath],response.responseData);
    
    if ([self.child respondsToSelector:@selector(responseClass)]) {
        self.responseData =  [[self.child responseClass] yy_modelWithDictionary:response.responseData];
    /**  
        200:請求成功之后的狀態(tài)碼,
        如果是10000就改成10000,根據(jù)自己的接口返回自行處理
    */
        if (self.responseData.errcode!=200) {
            response.msg = self.responseData.msg;
            [self failedOnCallingAPI:response withErrorType:APIManagerErrorTypeDefault];
            return;
        }
    } else {
        self.responseData = response.responseData;
    }
    
    if ([self.validator respondsToSelector:@selector(manager:isCorrectWithCallBackData:)] && ![self.validator manager:self isCorrectWithCallBackData:self.responseData]) {
        [self failedOnCallingAPI:response withErrorType:APIManagerErrorTypeNoContent];
    } else {
        if ([self.child respondsToSelector:@selector(reformData)]) {
            [self.child reformData];
        }
        [self.delegate managerCallAPIDidSuccess:self];
    }
}

- (void)failedOnCallingAPI:(BaseAPIResponse *)response withErrorType:(APIManagerErrorType)errorType {
    if (self.hudSuperView) {
        [MBProgressHUD hideLoadingHUD];
    }
    
    self.errorType = errorType;
    self.msg = response.msg;
    [self removeRequestIdWithRequestID:response.requestId];
    switch (errorType) {
    /** 
        此處就是請求失敗返回的 各種狀態(tài),根據(jù)需求,自行修改處理
     */
        case APIManagerErrorTypeDefault:
            self.errorMessage = response.msg;
            break;
        case APIManagerErrorTypeSuccess:
            break;
        case APIManagerErrorTypeNoContent:
            break;
        case APIManagerErrorTypeParamsError:
            break;
        case APIManagerErrorTypeTimeout:
            self.msg = Tip_RequestOutTime;
            break;
        case APIManagerErrorTypeNoNetWork:
            self.msg = Tip_NoNetwork;
            break;
        case APIManagerErrorLoginTimeout:
            self.msg = Tip_LoginTimeOut;
            break;
        default:
            break;
    }
    if (self.errorType==APIManagerErrorLoginTimeout) {
        if (!self.reloginCount && ![self isKindOfClass:[LoginRequest class]]) {
            self.reloginCount++;
            [LoginRequest autoReloginSuccess:^{
                [self loadDataWithHUDOnView:self.hudSuperView];
            } failure:^{
                [UserManager removeLocalUserLoginInfo];
                [kAppDelegate loadLoginVC];
            }];
        } else {
            [UserManager removeLocalUserLoginInfo];
            [kAppDelegate loadLoginVC];
        }
    } else {
        [self.delegate managerCallAPIDidFailed:self];
        if (self.hudSuperView && !self.disableErrorTip) {
            [MBProgressHUD showMsgHUD:response.msg];
        }
    }
}

PS:以上就是發(fā)起請求之后的處理的步驟,細(xì)心的同學(xué)應(yīng)該注意到了,這里成功、失敗提到的是" BaseAPIResponse"這個類,所謂Response就是響應(yīng)的意思,只有發(fā)起請求request之后,通過服務(wù)器響應(yīng)response,才能判斷請求是成功還是失敗,所以響應(yīng)單獨抽個類處理。

#pragma mark - private methods
- (void)cancelAllRequests {
    [[BaseAPIProxy sharedInstance] cancelRequestWithRequestIDList:self.requestIdList];
    [self.requestIdList removeAllObjects];
}

- (void)cancelRequestWithRequestId:(NSInteger)requestID {
    [self removeRequestIdWithRequestID:requestID];
    [[BaseAPIProxy sharedInstance] cancelRequestWithRequestID:@(requestID)];
}

- (void)removeRequestIdWithRequestID:(NSInteger)requestId {
    NSNumber *requestIDToRemove = nil;
    for (NSNumber *storedRequestId in self.requestIdList) {
        if ([storedRequestId integerValue] == requestId) {
            requestIDToRemove = storedRequestId;
        }
    }
    if (requestIDToRemove) {
        [self.requestIdList removeObject:requestIDToRemove];
    }
}
    /**
        參數(shù)的簽名加密,上面提到過,
        根據(jù)自己的項目需求做處理,如果接口無特殊要求,
        請繞道自動忽略。
     */
- (NSDictionary *)signatureParams:(NSDictionary *)params {
    if (![params isKindOfClass:[NSDictionary class]]) {
        return nil;
    }
    /** 處理簽名方法 */
    、、、
    return newParams;
}

#pragma mark - getters and setters
- (BOOL)isReachable {
    if ([AFNetworkReachabilityManager sharedManager].networkReachabilityStatus == AFNetworkReachabilityStatusUnknown) {
        return YES;
    } else {
        return [[AFNetworkReachabilityManager sharedManager] isReachable];
    }
}

- (NSMutableArray *)requestIdList {
    if (_requestIdList == nil) {
        _requestIdList = [[NSMutableArray alloc] init];
    }
    return _requestIdList;
}
@end

下面說一下響應(yīng)的處理,根據(jù)定義的變量名稱,想必大家一目了然,就是失敗的幾種情況,狀態(tài)碼之類的。
BaseAPIResponse.h

#import <Foundation/Foundation.h>

typedef NS_ENUM (NSUInteger, APIManagerErrorType){
    APIManagerErrorTypeDefault,       //沒有產(chǎn)生過API請求,這個是manager的默認(rèn)狀態(tài)。
    APIManagerErrorTypeSuccess,       //API請求成功且返回數(shù)據(jù)正確,此時manager的數(shù)據(jù)是可以直接拿來使用的。
    APIManagerErrorTypeNoContent,     //API請求成功但返回數(shù)據(jù)不正確。如果回調(diào)數(shù)據(jù)驗證函數(shù)返回值為NO,manager的狀態(tài)就會是這個。
    APIManagerErrorTypeParamsError,   //參數(shù)錯誤,此時manager不會調(diào)用API,因為參數(shù)驗證是在調(diào)用API之前做的。
    APIManagerErrorTypeTimeout,       //請求超時。ApiProxy設(shè)置的是20秒超時,具體超時時間的設(shè)置請自己去看ApiProxy的相關(guān)代碼。
    APIManagerErrorTypeNoNetWork,     //網(wǎng)絡(luò)不通。在調(diào)用API之前會判斷一下當(dāng)前網(wǎng)絡(luò)是否通暢,這個也是在調(diào)用API之前驗證的,和上面超時的狀態(tài)是有區(qū)別的。
    APIManagerErrorLoginTimeout,       //登錄超時
};

@interface BaseAPIResponse : NSObject

@property (nonatomic, assign, readonly)NSInteger requestId;
@property (nonatomic, copy)NSString *msg;
@property (nonatomic, assign, readonly)APIManagerErrorType errorType;
@property (nonatomic, assign, readonly)NSInteger httpStatusCode;
@property (nonatomic, assign, readonly)id responseData;

- (instancetype)initWithRequestId:(NSNumber *)requestId responseObject:(id)responseObject urlResponse:(NSHTTPURLResponse *)urlResponse;
- (instancetype)initWithRequestId:(NSNumber *)requestId urlResponse:(NSHTTPURLResponse *)urlResponse error:(NSError *)error;

@end

/* 根據(jù)服務(wù)器返回數(shù)據(jù)結(jié)構(gòu)設(shè)計基本數(shù)據(jù),如狀態(tài)碼、提示信息等*/
@interface BaseResponse : NSObject
@property(nonatomic, assign) NSInteger errcode;
@property(nonatomic, copy) NSString *msg;
@end

    /** 
      這里是針對分頁請求做的處理,
      前文提到,次方案分頁請求是單獨抽出來一個類來做處理的,
      并且發(fā)起請求的時候,只需要遵守BaseAPIRequest中三個必須實現(xiàn)的
      三個(上傳的參數(shù)、請求成功、失敗的回調(diào))協(xié)議中的
      一個(上傳的參數(shù))即可,因為,內(nèi)部已做處理。
 */
@interface PageModel : NSObject
@property(nonatomic,copy)NSArray *list;
@property(nonatomic,copy)NSNumber *totalPage;
@property(nonatomic,copy)NSNumber *totalRow;
@property(nonatomic,copy)NSNumber *total_count;
@end
#import "BaseAPIResponse.h"

@interface BaseAPIResponse ()
@property (nonatomic, assign, readwrite)NSInteger httpStatusCode;
@property (nonatomic, assign, readwrite)APIManagerErrorType errorType;
@property (nonatomic, assign, readwrite)id responseData;
@end

@implementation BaseAPIResponse

- (instancetype)initWithRequestId:(NSNumber *)requestId responseObject:(id)responseObject urlResponse:(NSHTTPURLResponse *)urlResponse {
    self = [super init];
    if (self) {
        self.httpStatusCode = urlResponse.statusCode;
        self.responseData = responseObject;
    }
    return self;
}

- (instancetype)initWithRequestId:(NSNumber *)requestId urlResponse:(NSHTTPURLResponse *)urlResponse error:(NSError *)error {
    self = [super init];
    if (self) {
        self.httpStatusCode = urlResponse.statusCode;
        self.msg = [self handelError:error];
        if (self.httpStatusCode==11010) {
            self.errorType = APIManagerErrorLoginTimeout;
        }
    }
    return self;
}

- (NSString *)handelError:(NSError*)error {
    NSString *errorMsg = Tip_RequestError;
    if (error) {
        NSData *responseData = error.userInfo[@"com.alamofire.serialization.response.error.data"];
        if (responseData) {
            NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:NULL];
            if ([response isKindOfClass:[NSDictionary class]]) {
                errorMsg = safeString(response[@"msg"]);
                return errorMsg;
            }
        }
        if (safeNumber(error.userInfo[@"_kCFStreamErrorCodeKey"]).integerValue==-2102) {
            errorMsg = Tip_RequestOutTime;
        }
    }
    return errorMsg;
}

@end

@implementation BaseResponse


@end

 /**
      因為解析用的是YYModel來處理的,個人推薦,可以看看,簡單實用,很多都會幫你處理好,免去了解析的痛苦。
*/
@implementation PageModel
- (id)copyWithZone:(NSZone *)zone {
    return [self yy_modelCopy];
}
@end

到這里,兩個主要的類已經(jīng)說完了,我們很快就要成功了,大家 加油!下面說說BaseAPIProxy這個類:

#import <Foundation/Foundation.h>
#import "BaseAPIResponse.h"

typedef NS_ENUM (NSUInteger, APIManagerRequestType){
    APIManagerRequestTypeGet,
    APIManagerRequestTypePost,
    APIManagerRequestTypeUpload,
    APIManagerRequestTypeDelete,
    APIManagerRequestTypePut
};

typedef void(^APICallback)(BaseAPIResponse *response);

@interface BaseAPIProxy : NSObject

@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
/**  來個單例*/
+ (instancetype)sharedInstance;
/** 
    這里就是在文章開始說的那個BaseAPIRequest類中提到,
    發(fā)起請求之后,調(diào)用這個類,忘了的同學(xué)請上翻。??
    上文中也提到此處可以理解為對AF的封裝,
    每次都寫請求多麻煩啊,對吧?并且是GET還是POST的啊(所以,上面列舉了一堆枚舉。)?
    這里都做了判斷,沒有說的東西。
*/
- (NSInteger)callAPIWithRequestType:(APIManagerRequestType)requestType params:(NSDictionary *)params requestPath:(NSString *)requestPath uploadBlock:(void (^)(id <AFMultipartFormData> formData))uploadBlock success:(APICallback)success fail:(APICallback)fail;

- (void)cancelRequestWithRequestID:(NSNumber *)requestID;
- (void)cancelRequestWithRequestIDList:(NSArray *)requestIDList;

@end

BaseAPIProxy.m

#import "BaseAPIProxy.h"

#define kCookie @"Cookie"

@interface BaseAPIProxy ()
@property (nonatomic, strong) NSMutableDictionary *dispatchTable;
@property (nonatomic, strong) NSNumber *recordedRequestId;
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
@end

@implementation BaseAPIProxy

+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    static BaseAPIProxy *sharedInstance = nil;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[BaseAPIProxy alloc] init];
    });
    return sharedInstance;
}

- (NSInteger)callAPIWithRequestType:(APIManagerRequestType)requestType params:(NSDictionary *)params requestPath:(NSString *)requestPath uploadBlock:(void (^)(id <AFMultipartFormData> formData))uploadBlock success:(APICallback)success fail:(APICallback)fail {
    NSString *urlString = [NSString stringWithFormat:@"%@%@",BaseUrl,requestPath];
    NSNumber *requestId = [self callApi:urlString requestType:requestType params:params uploadBlock:uploadBlock success:success fail:fail];
    return [requestId integerValue];
}

- (void)cancelRequestWithRequestID:(NSNumber *)requestID {
    NSURLSessionDataTask *dataTask = self.dispatchTable[requestID];
    [dataTask cancel];
    [self.dispatchTable removeObjectForKey:requestID];
}

- (void)cancelRequestWithRequestIDList:(NSArray *)requestIDList {
    for (NSNumber *requestId in requestIDList) {
        [self cancelRequestWithRequestID:requestId];
    }
}
/** 這個函數(shù)存在的意義在于,如果將來要把AFNetworking換掉,只要修改這個函數(shù)的實現(xiàn)即可。 */
- (NSNumber *)callApi:(NSString *)URLString requestType:(APIManagerRequestType)requestType params:(NSDictionary *)params uploadBlock:(void (^)(id <AFMultipartFormData> formData))uploadBlock success:(APICallback)success fail:(APICallback)fail {
    // 之所以不用getter,是因為如果放到getter里面的話,每次調(diào)用self.recordedRequestId的時候值就都變了,違背了getter的初衷
    NSNumber *requestId = [self generateRequestId];
    
    self.sessionManager.requestSerializer = self.requestSerializer;
    self.sessionManager.requestSerializer.timeoutInterval = 10;
    self.sessionManager.responseSerializer = self.responseSerializer;
    [self setCookie];
    // 跑到這里的block的時候,就已經(jīng)是主線程了。
    NSURLSessionDataTask *dataTask;
    switch (requestType)
    {
        case APIManagerRequestTypeGet:
        {
            dataTask = [self.sessionManager GET:URLString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                [self handelFailRequest:requestId task:task error:error fail:fail];
            }];
        }
            break;
        case APIManagerRequestTypePost:
        {
            dataTask = [self.sessionManager POST:URLString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                [self handelFailRequest:requestId task:task error:error fail:fail];
            }];
        }
            break;
        case APIManagerRequestTypeUpload:
        {
            self.sessionManager.requestSerializer.timeoutInterval = 20;
            dataTask = [self.sessionManager POST:URLString parameters:params constructingBodyWithBlock:uploadBlock progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                [self handelFailRequest:requestId task:task error:error fail:fail];
            }];
        }
            break;
        case APIManagerRequestTypeDelete:
        {
            dataTask = [self.sessionManager DELETE:URLString parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                [self handelFailRequest:requestId task:task error:error fail:fail];
            }];
        }
            break;
        case APIManagerRequestTypePut:
        {
            dataTask = [self.sessionManager PUT:URLString parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                [self handelFailRequest:requestId task:task error:error fail:fail];
            }];
        }
            break;
        default:
            break;
    }
    
    self.dispatchTable[requestId] = dataTask;
    return requestId;
}

- (void)handelSuccessRequst:(NSNumber *)requestId task:(NSURLSessionDataTask *)task responseObject:(id)responseObject success:(APICallback)success {
    NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]];
    //存儲歸檔后的cookie
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setObject:cookiesData forKey:kCookie];
    [self setCookie];
    
    NSURLSessionDataTask *storedTask = self.dispatchTable[requestId];
    if (storedTask == nil) {
        // 如果這個task是被cancel的,那就不用處理回調(diào)了。
        return;
    } else {
        [self.dispatchTable removeObjectForKey:requestId];
    }
    
    BaseAPIResponse *response = [[BaseAPIResponse alloc] initWithRequestId:requestId responseObject:responseObject urlResponse:(NSHTTPURLResponse *)task.response];
    
    success?success(response):nil;
}

- (void)handelFailRequest:(NSNumber *)requestId task:(NSURLSessionDataTask *)task error:(NSError *)error fail:(APICallback)fail {
    NSURLSessionDataTask *storedTask = self.dispatchTable[requestId];
    if (storedTask == nil) {
        // 如果這個task是被cancel的,那就不用處理回調(diào)了。
        return;
    } else {
        [self.dispatchTable removeObjectForKey:requestId];
    }
    
    BaseAPIResponse *response = [[BaseAPIResponse alloc] initWithRequestId:requestId urlResponse:(NSHTTPURLResponse *)task.response error:error];
    
    fail?fail(response):nil;
}

并且對于請求成功之后也做了cookies處理

- (NSNumber *)generateRequestId {
    if (_recordedRequestId == nil) {
        _recordedRequestId = @(1);
    } else {
        if ([_recordedRequestId integerValue] == NSIntegerMax) {
            _recordedRequestId = @(1);
        } else {
            _recordedRequestId = @([_recordedRequestId integerValue] + 1);
        }
    }
    return _recordedRequestId;
}

- (void)setCookie {
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    if (![userDefaults objectForKey:kCookie]) {
        return;
    }
    //對取出的cookie進行反歸檔處理
    NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData:[userDefaults objectForKey:kCookie]];
    
    if (cookies) {
        //設(shè)置cookie
        NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        for (id cookie in cookies) {
            [cookieStorage setCookie:(NSHTTPCookie *)cookie];
        }
    }
}

#pragma mark - getters and setters
- (NSMutableDictionary *)dispatchTable {
    if (_dispatchTable == nil) {
        _dispatchTable = [[NSMutableDictionary alloc] init];
    }
    return _dispatchTable;
}

- (AFHTTPSessionManager *)sessionManager {
    if (_sessionManager == nil) {
        _sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:nil];
    }
    return _sessionManager;
}

- (NSString *)cookie {
    return safeString([[NSUserDefaults standardUserDefaults] valueForKey:kCookie]);
}
@end

PS:以上除了分頁請求就都介紹完了,但是革命尚未成功,同志而需努力!還差個分頁,大家 加油!
在此,我就不做過多的解釋了,注釋很清楚。

#import "BaseAPIRequest.h"

@protocol PageDelegate <NSObject>
@required
- (NSArray *)buildPageArray;
@end

@interface PageAPIRequest : BaseAPIRequest <APIManager>

/**
 *  當(dāng)前頁數(shù)
 */
@property (nonatomic, assign) NSUInteger                currentPage;
/**
 *  最終結(jié)果
 */
@property (nonatomic, strong) NSMutableArray*           listArray;
/**
 *  是否還有數(shù)據(jù),只要有數(shù)據(jù)返回,就認(rèn)為還有下一頁
 */
@property (nonatomic, assign) BOOL                      moreData;

/**
 *  清空listArray,currentPage = 1
 */
- (void)reload;

/**
 *  清空listArray,currentPage = 1
 */
- (void)reloadOnView:(UIView *)view;

/**
 *  每頁多少條數(shù)據(jù),默認(rèn)20
 */
- (NSUInteger)pageSize;

@end
#import "PageAPIRequest.h"

@implementation PageAPIRequest

- (id)init {
    self = [super init];
    if (self) {
        self.currentPage = 1;
    }
    return self;
}

- (id)initWithDelegate:(id)delegate paramSource:(id)paramSource {
    self = [super initWithDelegate:delegate paramSource:paramSource];
    if (self) {
        self.currentPage = 1;
    }
    return self;
}

- (NSUInteger)pageSize {
    return 10;
}

/** 這里呢,就是根據(jù)自己的接口來定義分頁,看接口是那個字段,做修改即可。原理就是根據(jù)key會自取。*/
- (NSDictionary *)reformParamsForApi:(NSDictionary *)params {
    NSMutableDictionary *newParmas = params?[params mutableCopy]:[NSMutableDictionary dictionary];
    [newParmas setObject:[NSNumber numberWithInteger:self.currentPage] forKey:@"page"];
    [newParmas setObject:[NSNumber numberWithInteger:[self pageSize]] forKey:@"pageSize"];
    、、、
    [newParmas setObject:[NSNumber numberWithInteger:[self pageSize]*(self.currentPage-1)] forKey:@"page_start"];
    return newParmas;
}

- (void)reformData {
    if (_currentPage == 1) {
        [self.listArray removeAllObjects];
    }
    
    NSArray *array = nil;
    if ([self.responseData respondsToSelector:@selector(buildPageArray)]) {
        array = [self.responseData performSelector:@selector(buildPageArray)];
        
    } else if ([self.responseData isKindOfClass:[NSArray class]]) {
        array = (NSArray *)self.responseData;
    }
    self.moreData = NO;
    if ([array count] > 0) {
        self.moreData = [array count] >= [self pageSize] ? YES : NO;
        self.currentPage ++;
        [self.listArray addObjectsFromArray:array];
    }
}

- (void)reload {
    self.currentPage = 1;
    [self loadDataWithHUDOnView:nil];
}

- (void)reloadOnView:(UIView *)view {
    self.currentPage = 1;
    [self loadDataWithHUDOnView:view];
}

- (NSString *)requestPath {
    return [self.child requestPath];
}

- (APIManagerRequestType)requestType {
    return [self.child requestType];
}

- (NSMutableArray *)listArray {
    if (!_listArray) {
        _listArray = [NSMutableArray array];
    }
    return _listArray;
}
@end

PS:好了,大功告成,到這里,對于此方案的設(shè)計就全部講解完畢,還記得如何使用嗎?記住:每個界面只需要實現(xiàn)三個代理方法即可,當(dāng)分頁的時候,只需要一個即可。

--- 下面舉兩個例子---

因為文章中提到,次方案的設(shè)計是網(wǎng)絡(luò)層的抽離,所以,每個請求都需要單獨抽一個類出來:

如果有這樣的一個接口:
{
"errcode": 200,
"msg": "操作成功",
"data": [
{
"id": "1",
"name": "1231",
"price": "123",
"img_url": "123"
},
{
"id": "2",
"name": "123",
"price": "123",
"img_url": "123"
},
]
}

這是一個簡單的商品列表,沒有分頁,當(dāng)我們創(chuàng)建請求類的時候繼承的是BaseAPIRequest這個類。

BaseAPIRequest.h

#import "BaseAPIRequest.h"
/**
  創(chuàng)建請求類的時候繼承BaseAPIRequest這個類,
  并且遵守<APIManager>協(xié)議,為什么?因為我們要實現(xiàn)三個協(xié)議方法啊。
  千萬不要忘了?。。?*/
/** 請求 */
@interface GoodsListRequest : BaseAPIRequest <APIManager>

@end

/** 響應(yīng)*/

/**
    因為我們需要對請求成功之后,服務(wù)器返回的數(shù)據(jù)進行賦值,
    所以才需要創(chuàng)建響應(yīng)方法,
    如果這個接口只是上傳幾個參數(shù)(比如修改密碼,成功了就是成功了,不需要返回什么),
    那么我們無需再寫Response的響應(yīng)處理了,
    (說白了,就是就是下面的這個方法就不用寫了。)
 */
@interface GoodsListResponse : BaseResponse
/** 
    為什么定義數(shù)組呢?因為服務(wù)器返回的就是數(shù)組啊,
    如果返回的是對象,那么此處就定義一個model唄。
*/
@property(nonatomic,strong)NSArray *data;
// @property(nonatomic,strong)UserModel *data;
@end

BaseAPIRequest.m

#import "GoodsListRequest.h"

@implementation GoodsListRequest
/** 
    此處就是請求的接口,我的處理方式是定義一個.h類,
    專門放接口的,好修改。

    //商品列表
    #define Goods_List_Url                  @"/mall/shopList" 

    這里為啥只有一小段接口,前面的都是一樣的,已經(jīng)封裝在BaseAPIProxy類中,
    忘了的同學(xué)請上翻??。
*/
- (NSString *)requestPath {
    return Goods_List_Url;
}
/** 你是什么請求方式,get、post..,根據(jù)接口,自行修改。*/
- (APIManagerRequestType)requestType {
    return APIManagerRequestTypePost;
}
/** 
    請求成功的響應(yīng)類,
    因為需要對請求成功之后的數(shù)據(jù)做處理,所有才會有這個類,
    名字對應(yīng)好即可。
    在.h中,我有做了說明,
    如果這個接口只是上傳幾個參數(shù),
    那么我們無需再寫Response的響應(yīng)處理了,
    將響應(yīng)的類改為BaseResponse即可。
*/
- (Class)responseClass {
    return [GoodsListResponse class];
}
@end

@implementation GoodsListResponse

/** 這里我用的是YYModel解析,這是YYModel的方法,*/

YYModel使用起來還是很簡單的,免去了很多解析的時間,
想必大部分人都知道,要么就是MJExtension,使用方法差不多類似??纯淳蜁昧?。

+ (NSDictionary *)modelContainerPropertyGenericClass {
    return @{@"data" : [GoodsModel class]};
}
@end

PS:好了,到這里,一個接口的請求就寫完了,那么在Controller里面如何實現(xiàn)?直接上代碼:

1、首頁你得把這個方法初始化吧,要不鬼知道你這是哪里來的逗比請求。

 // 1>導(dǎo)入頭文件 ,定義請求方法
@property(nonatomic,strong)GoodsListRequest *goodsListRequest;
 // 2>初始化
- (GoodsListRequest *)goodsListRequest {
    if (!_goodsListRequest) {
/** 
    注意:這里并不是簡單的init初始化,你是需要遵守三個協(xié)議方法的,所以這里有兩個self,因為文章開頭我就講了,忘了的同學(xué)請上翻??*/
        _goodsListRequest = [[GoodsListRequest alloc] initWithDelegate:self paramSource:self];
    }
    return _goodsListRequest;
}

2、發(fā)起請求

/**  
      這里self.contentView就是加載的小菊花顯示在哪個視圖上,
      因為我這里最底層的視圖是自定義的contentView,根據(jù)需求、用戶體驗,可自行修改視圖。
    */
[self.goodsListRequest loadDataWithHUDOnView:self.contentView];

3、遵守三個協(xié)議,忘了的同學(xué)請繼續(xù)上翻??,我已經(jīng)說明好幾次了,應(yīng)該都能記住了,不管如何實現(xiàn)能否掌握了解,但只要寫好請求,實現(xiàn)三個協(xié)議方法,基本就能成功了。

#pragma mark - APIManagerParamSourceDelegate
- (NSDictionary *)paramsForApi:(BaseAPIRequest *)request {
/** 如果請求的接口需要傳參,那么在此return。 */
    if (request == self.addCartRequest) {
        return @{@"goods_id":self.operatingGoods.goods_id,
                 @"number":@"1"};
    }
/** 文章中已經(jīng)提到,如果該請求接口沒有參數(shù),直接返回nil即可。 */
    return nil;
}
/** 請求之后的,成功、失敗的回調(diào)。 */
#pragma mark - APIManagerApiCallBackDelegate
- (void)managerCallAPIDidSuccess:(BaseAPIRequest *)request {
    if (request == self.goodsListRequest) {
/** 
    此處就是賦值操作,將請求下來的數(shù)據(jù)源,
    賦值給tableView的數(shù)據(jù)源,然后刷新表格即可。
 */
        self.shopTableView.dataArray = [[request.responseData valueForKey:@"data"] mutableCopy];

/**
    注意request.responseData接收的數(shù)據(jù)格式,因為此接口返回的是數(shù)組,所以這么寫。
    如果返回的是對象,很簡單:

/**  隨便舉個例子*/
    * self.userData:模型model 
              (將返回的對象中的數(shù)據(jù)賦值給model,然后取model中的各個字段,賦值即可。
                當(dāng)然有表格的界面,在請求成功之后記得刷新表格。)

    * data : 此處的data為定義的model。
          文章中那個商品列表的請求提到,如果返回的是數(shù)組就定義數(shù)組,
          如果是對象就定義model。
      self.userData = [request.responseData valueForKey:@"data"];
*/
        [self.shopTableView reloadData];
        return;
    }
/** 此處對應(yīng)請求的API接口,成功之后的回調(diào)*/
    if (request == self.addCartRequest) {
        [MBProgressHUD showMsgHUD:@"加入購物車成功"];
        return;
    }
}
/** 失敗,可以不寫什么,因為返回的是return,根據(jù)需求自行處理吧。 */
- (void)managerCallAPIDidFailed:(BaseAPIRequest *)request {
}

以上就是普通的接口請求,如果此接口為分頁請求,文章中已經(jīng)提到,只需要遵守一個參數(shù)的協(xié)議(APIManagerParamSourceDelegate)即可,這里就不在累贅了。

PS:寫了一下午,終于是搞完了,到這里,所有功能已全部講完,有錯誤的地方,請大神指正。個人認(rèn)為此框架使用起來還是超級簡單的,免去了Controler里面大量的代碼,留給更多的空間來處理邏輯。也希望小小的一篇文章能幫助更多的人,一起努力,未來、我們都會是全棧工程師。大家加油!

代碼:https://github.com/Baiyongyu/iOS-AFNetworking-.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,366評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,715評論 19 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,556評論 6 13
  • 剛剛打開簡書,發(fā)現(xiàn)首頁“簡書大學(xué)堂”推出了一系列的雙十一活動,有30天帶你早讀,有365天堅持寫作,還有領(lǐng)讀...
    紅犄角Alisa閱讀 603評論 7 11
  • 翻開空間看到有什么更新的內(nèi)容,瞬間冒出一種我的好友真是遍布各地牛逼。微商賣化妝品賣小黃片賣視頻賣題庫等等一大堆。 ...
    南安怎么安閱讀 271評論 0 0

友情鏈接更多精彩內(nèi)容