前言
網(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里面大量的代碼,留給更多的空間來處理邏輯。也希望小小的一篇文章能幫助更多的人,一起努力,未來、我們都會是全棧工程師。大家加油!