更新:梳理了庫中的耦合文件,可以直接提取網(wǎng)絡(luò)庫文件夾進(jìn)行使用,優(yōu)化了緩存設(shè)計.
鳴謝:本人是在認(rèn)真研讀casa的iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計方案之后,得出的相應(yīng)的思路,并在此基礎(chǔ)上做出了自己的需求延展.在此十分感謝這位反革命工程師的真知灼見!
首先,什么是離散化管理?在鳴謝的這篇文章里,casa已經(jīng)做出了一個比較明確的解釋:每一個請求的API都對應(yīng)一個類來管理,不同于將請求的url,參數(shù)等都放入一個方法(又稱集約式管理)中來管理.好處顯而易見:便于維護(hù),控制.
集約式是這樣的:
[manager GET:url parameters:param progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSDictionary* responseObject) {
success ? success(responseObject) : nil;
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
failure ? failure(error) : nil;
NSLog(@"請求失敗-->%@",error);
}];
離散化是這樣的:
@implementation TestAPIManger
//請求方式
- (EWRequestType)requestType{
return EWAPIRequestTypeGet;
}
//請求的方法名
- (NSString *)requestMethod{
return @"video";
}
//請求需要的參數(shù)
- (NSDictionary *)params{
return @{@"type":@"JSON"};
}
//是否需要緩存
- (NSNumber *)shouldCache{
return @180;
}
//額外參數(shù),根據(jù)需求而定
- (NSString *)memberCode{
return @"";
}
//是否需要加載動畫
- (NSDictionary *)animationTargetAction{
return @{
EWRequestAnimationTarget : @"ZNRequestAnimation",
EWShowHudAnimation : @"showHudAnimation",
EWHideHudAtWindow : @"hideHudAtWindow"
};
}
//是否需要拼接請求頭
- (NSDictionary *)headerDict{
return nil;
}
@end
離散化的調(diào)用方法是這樣的,這里使用代理的方式來回調(diào)結(jié)果,目的是控制靈活性,方便管理,bug排查:
TestAPIManger *testApi = [[TestAPIManger alloc] init];
testApi.delegate = self;
[testApi loadData];
這里來進(jìn)行一波解釋:
TestAPIManger
->進(jìn)行網(wǎng)絡(luò)請求的實例,封裝了請求需要的url,參數(shù)等
testApi.delegate
->進(jìn)行網(wǎng)路請求數(shù)據(jù)回調(diào)的代理
loadData
->開啟網(wǎng)絡(luò)請求的方法.
正式開始封裝之路:
疑問1:如何設(shè)計這個APIManager?
這里是設(shè)計了一個EWAPIBaseManager,這個類的作用是定義請求APIManager請求的基本方法,作為一個父類,之后的每個請求實例都繼承自這個類.
這里設(shè)計出來的樣子暫時是這個樣子:
//回調(diào)的代理,需要遵守EWAPICallBackProtocol協(xié)議
@property (nonatomic , weak) id<EWAPICallBackProtocol> delegate;
//遵守協(xié)議的子類,須遵守EWAPIManagerProtocol協(xié)議
@property (nonatomic , weak) NSObject<EWAPIManagerProtocol> *childManager;
//自定義response用來統(tǒng)一保存數(shù)據(jù)和error
@property (nonatomic , strong) EWResponse *response;
//外部傳入的參數(shù)
@property (strong,nonatomic) NSMutableDictionary *outerParams;
//是否需要動畫
@property (nonatomic , strong) NSDictionary *animationTargetAction;
//數(shù)據(jù)過濾的方法,必須要遵守EWDataFilterProtocol協(xié)議
- (id)filterDataWithFilter:(id<EWDataFilterProtocol>)filter;
//是否需要緩存
- (NSNumber *)shouldCache;
/**
* 加載數(shù)據(jù)
*/
- (void)loadData;
/**
* 取消請求
*/
- (void)cancelAllRequest;
其中加載數(shù)據(jù)作為一個基本功能被放到了這里,方法名為loadData
.
這里用到了幾個協(xié)議:
EWAPICallBackProtocol
:完成請求回調(diào)的協(xié)議
EWAPIManagerProtocol
:管理每個請求的參數(shù),url等
EWDataFilterProtocol
:定義了數(shù)據(jù)過濾的方法
疑問2:每一個APIManager如何管理請求URL和參數(shù)?
解決方式:聲明一個協(xié)議EWAPIManagerProtocol
,聲明如下方法
typedef NS_ENUM(NSInteger,EWRequestType){
EWAPIRequestTypeGet = 0,
EWAPIRequestTypePost = 1,
EWAPIRequestTypeUploadImage = 2
};
@protocol EWAPIManagerProtocol <NSObject>
@required
//請求方式
- (EWRequestType)requestType;
//請求的參數(shù)
- (NSDictionary *)params;
//請求的方法名
- (NSString *)requestMethod;
//請求后完整的拼接參數(shù)
- (NSDictionary*)paramsForAPI;
//自定義的requestheader
- (NSDictionary *)headerDict;
@optional
//物業(yè)接口可能會有membercode
- (NSString *)memberCode;
@end
然后創(chuàng)建一個NSObject類,遵守這個協(xié)議,就是上面的TestAPIManger
,并實現(xiàn)協(xié)議中的方法,那么這些參數(shù)都被保存在了這個TestAPIManger
了.
疑問3:如何將APIManager保存的參數(shù)傳遞到網(wǎng)絡(luò)請求中?
這里我要重提一下casa的觀點(diǎn),離散化的網(wǎng)絡(luò)層其實本質(zhì)是集約調(diào)用,我們只不過是在底層通過delegate將返回的數(shù)據(jù)進(jìn)行了轉(zhuǎn)發(fā)而已,因為底層變動不大,所以如此做無傷大雅.
在上面的loadData
方法中,是如下實現(xiàn)方式:
/**
* 執(zhí)行請求任務(wù)
*/
- (void)loadData{
switch (self.childManager.requestType) {
case EWAPIRequestTypeGet:
APIRequest(Get)
break;
case EWAPIRequestTypePost:
APIRequest(Post)
break;
case EWAPIRequestTypeUploadImage:
APIRequest(PostImage)
break;
default:
break;
}
}
其中APIRequest()
是一個宏,該宏實現(xiàn)了網(wǎng)絡(luò)請求:
/**
* 定義完成請求的宏
*/
#define APIRequest(requestType) \
{\
EW_WeakSelf\
[self startRequestAnimation];\開啟動畫
[self.apiRequest sendRequestBy##requestType##WithParams:self.childManager.paramsForAPI success:^(EWResponse *response) {\
[weakSelf cancellReqeustAnimation];\關(guān)閉動畫
[weakSelf requestSuccess:response];\
} fail:^(EWResponse *response) {\
[weakSelf cancellReqeustAnimation];\關(guān)閉動畫
[weakSelf requestFailed:response];\
}];\
}
在這里self.childManager.paramsForAPI
就將APIManager中的參數(shù)傳遞過去了.至于原理,在于將作為EWBaseAPIManager
的init方法重寫了,當(dāng)我們初始化子類TestAPIManager
的時候,父類EWBaseAPIManager
中的childManager
就已經(jīng)成為了TestAPIManager
:
- (instancetype)init
{
self = [super init];
if (self) {
//初始化的時候,childManager即為當(dāng)前的子類,完成請求參數(shù)的傳遞
if ([self conformsToProtocol:@protocol(EWAPIManagerProtocol)]) {
self.childManager = (NSObject<EWAPIManagerProtocol> *)self;
}
}
return self;
}
疑問4:底層如何進(jìn)行數(shù)據(jù)請求?
這里我設(shè)計了一個分發(fā)請求的類EWAPIRequest
,里面目前只定義了三個方法:
/**
* get請求
*
* @param params 傳入的參數(shù),必須包含url,請求類型,參數(shù),以及cache時間(沒有就填@0)
* @param success 請求成功后的回調(diào)(請將請求成功后想做的事情寫到這個block中)
* @param failure 請求失敗后的回調(diào)(請將請求失敗后想做的事情寫到這個block中)
*/
- (NSURLSessionDataTask *)sendRequestByGetWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;
/**
* post請求
*
* @param params 傳入的參數(shù),必須包含url,請求類型,參數(shù),以及cache時間(沒有就填@0)
* @param success 請求成功后的回調(diào)(請將請求成功后想做的事情寫到這個block中)
* @param failure 請求失敗后的回調(diào)(請將請求失敗后想做的事情寫到這個block中)
*/
- (NSURLSessionDataTask *)sendRequestByPostWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;
/**
* 圖片上傳
*
* @param params 傳入的參數(shù),必須包含url,圖片內(nèi)容,圖片key為EWUploadImageKey
* @param success 請求成功后的回調(diào)(請將請求成功后想做的事情寫到這個block中)
* @param failure 請求失敗后的回調(diào)(請將請求失敗后想做的事情寫到這個block中)
*/
- (NSURLSessionDataTask *)sendRequestByPostImageWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;
調(diào)用的地方在上方的請求宏中:
self.apiRequest sendRequestBy##requestType##WithParams:self.childManager.paramsForAPI success:^(EWResponse *response)
進(jìn)入到EWAPIRequest
的方法實現(xiàn)中,我們可以看到里面是這樣實現(xiàn)的:
NSURLSessionDataTask *dataTask = nil;
//通過工廠類獲得請求的實例,實例必須遵循這個請求的協(xié)議
//這里采用硬編碼的方式,決定到底是用什么庫來進(jìn)行網(wǎng)絡(luò)請求,目的在于方便切換網(wǎng)絡(luò)庫
//KEWRequestByAFN表示采用AFN這個網(wǎng)絡(luò)庫請求數(shù)據(jù)
id<EWNetworkRequestProtocol> requestInstance = [[EWRequestInstanceFactory shareInstance] requestInstance:KEWRequestByAFN];
//請求數(shù)據(jù)
dataTask = [requestInstance requestByGetWithParams:params success:^(id responseObject) {
//生成統(tǒng)一管理網(wǎng)絡(luò)數(shù)據(jù)的response
//存入回調(diào)的數(shù)據(jù)
EWResponse *response = [[EWResponse alloc] initWithResopnseObject:responseObject andError:nil];
//回調(diào)這個response
success ? success(response) : nil;
} fail:^(NSError *error) {
//存入錯誤信息
EWResponse *errorResponse = [[EWResponse alloc] initWithResopnseObject:nil andError:error];
//回調(diào)這個response
failure ? failure(errorResponse) : nil;
SLog(@"請求失敗-->%@",error);
}];
這里涉及到了幾個協(xié)議和類,一一解釋一下
EWNetworkRequestProtocol
:這個協(xié)議定義了最底層網(wǎng)絡(luò)請求庫需要遵守的方法,這里我用的AFN作為底層請求庫.
KEWRequestByAFN
:這是個const常量,表示當(dāng)前的請求庫是基于AFN的
EWRequestInstanceFactory
:這是個工廠類,為了返回遵守EWNetworkRequestProtocol
協(xié)議的網(wǎng)絡(luò)庫的實例,底層是通過反射KEWRequestByAFN
這個字符串獲得請求的實例,如果你要切換網(wǎng)絡(luò)庫,只需要新增一個請求類并且再定義一個const常量,在EWRequestInstanceFactory
中替換KEWRequestByAFN
即可.
EWResponse
:這個類的作用是用來統(tǒng)一保存請求的數(shù)據(jù)和錯誤信息
拿到請求的實例requestInstance
之后就調(diào)用EWNetworkRequestProtocol
中的requestByGetWithParams
方法來進(jìn)行網(wǎng)絡(luò)請求.之后就是將參數(shù)傳入AFN請求類中實現(xiàn)最終的請求,并回調(diào)結(jié)果.
疑問5:回調(diào)結(jié)果的處理?
在EWAPIBaseManager
中,我用了兩個私有方法在請求的宏里對回調(diào)結(jié)果進(jìn)行轉(zhuǎn)發(fā),然后將結(jié)果回調(diào)給EWAPICallBackProtocol
協(xié)議中的方法:managerCallBackDidSuccess
,managerCallBackDidFailed
.
//回調(diào)成功的response,里面保存了請求成功的數(shù)據(jù)
- (void)requestSuccess:(EWResponse *)response{
//將response賦值給apiManager
self.response = response;
if ([self.delegate respondsToSelector:@selector(managerCallBackDidSuccess:)]) {
[self.delegate managerCallBackDidSuccess:self];
}
}
//回調(diào)失敗的response,里面保存了請求失敗的錯誤信息
- (void)requestFailed:(EWResponse *)response{
//將response賦值給apiManager
self.response = response;
if ([self.delegate respondsToSelector:@selector(managerCallBackDidFailed:)]) {
[self.delegate managerCallBackDidFailed:self];
}
}
就這樣,就實現(xiàn)了整個網(wǎng)絡(luò)請求的過程.至于這里為什么是回調(diào)response而不是直接回調(diào)responseObject,原因是用response可以統(tǒng)一管理回調(diào)數(shù)據(jù)和錯誤信息,就不需要再定義responseObject和error的變量了.
在這個庫里面我還添加了加載動畫,緩存,數(shù)據(jù)過濾等功能,有興趣可以自己研究下,demo在這里.