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