更新:梳理了库中的耦合文件,可以直接提取网络库文件夹进行使用,优化了缓存设计.
鸣谢:本人是在认真研读casa的iOS应用架构谈 网络层设计方案之后,得出的相应的思路,并在此基础上做出了自己的需求延展.在此十分感谢这位反革命工程师的真知灼见!
首先,什么是离散化管理?在鸣谢的这篇文章里,casa已经做出了一个比较明确的解释:每一个请求的API都对应一个类来管理,不同于将请求的url,参数等都放入一个方法(又称集约式管理)中来管理.好处显而易见:便于维护,控制.
集约式是这样的:
[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";
}
//请求需要的参数
- (NSDictionary *)params{
return @{@"type":@"JSON"};
}
//是否需要缓存
- (NSNumber *)shouldCache{
return @180;
}
//额外参数,根据需求而定
- (NSString *)memberCode{
return @"";
}
//是否需要加载动画
- (NSDictionary *)animationTargetAction{
return @{
EWRequestAnimationTarget : @"ZNRequestAnimation",
EWShowHudAnimation : @"showHudAnimation",
EWHideHudAtWindow : @"hideHudAtWindow"
};
}
//是否需要拼接请求头
- (NSDictionary *)headerDict{
return nil;
}
@end
离散化的调用方法是这样的,这里使用代理的方式来回调结果,目的是控制灵活性,方便管理,bug排查:
TestAPIManger *testApi = [[TestAPIManger alloc] init];
testApi.delegate = self;
[testApi loadData];
这里来进行一波解释:
TestAPIManger
->进行网络请求的实例,封装了请求需要的url,参数等
testApi.delegate
->进行网路请求数据回调的代理
loadData
->开启网络请求的方法.
正式开始封装之路:
疑问1:如何设计这个APIManager?
这里是设计了一个EWAPIBaseManager,这个类的作用是定义请求APIManager请求的基本方法,作为一个父类,之后的每个请求实例都继承自这个类.
这里设计出来的样子暂时是这个样子:
//回调的代理,需要遵守EWAPICallBackProtocol协议
@property (nonatomic , weak) id delegate;
//遵守协议的子类,须遵守EWAPIManagerProtocol协议
@property (nonatomic , weak) NSObject *childManager;
//自定义response用来统一保存数据和error
@property (nonatomic , strong) EWResponse *response;
//外部传入的参数
@property (strong,nonatomic) NSMutableDictionary *outerParams;
//是否需要动画
@property (nonatomic , strong) NSDictionary *animationTargetAction;
//数据过滤的方法,必须要遵守EWDataFilterProtocol协议
- (id)filterDataWithFilter:(id)filter;
//是否需要缓存
- (NSNumber *)shouldCache;
/**
* 加载数据
*/
- (void)loadData;
/**
* 取消请求
*/
- (void)cancelAllRequest;
其中加载数据作为一个基本功能被放到了这里,方法名为loadData
.
这里用到了几个协议:
EWAPICallBackProtocol
:完成请求回调的协议
EWAPIManagerProtocol
:管理每个请求的参数,url等
EWDataFilterProtocol
:定义了数据过滤的方法
疑问2:每一个APIManager如何管理请求URL和参数?
解决方式:声明一个协议EWAPIManagerProtocol
,声明如下方法
typedef NS_ENUM(NSInteger,EWRequestType){
EWAPIRequestTypeGet = 0,
EWAPIRequestTypePost = 1,
EWAPIRequestTypeUploadImage = 2
};
@protocol EWAPIManagerProtocol
@required
//请求方式
- (EWRequestType)requestType;
//请求的参数
- (NSDictionary *)params;
//请求的方法名
- (NSString *)requestMethod;
//请求后完整的拼接参数
- (NSDictionary*)paramsForAPI;
//自定义的requestheader
- (NSDictionary *)headerDict;
@optional
//物业接口可能会有membercode
- (NSString *)memberCode;
@end
然后创建一个NSObject类,遵守这个协议,就是上面的TestAPIManger
,并实现协议中的方法,那么这些参数都被保存在了这个TestAPIManger
了.
疑问3:如何将APIManager保存的参数传递到网络请求中?
这里我要重提一下casa的观点,离散化的网络层其实本质是集约调用,我们只不过是在底层通过delegate将返回的数据进行了转发而已,因为底层变动不大,所以如此做无伤大雅.
在上面的loadData
方法中,是如下实现方式:
/**
* 执行请求任务
*/
- (void)loadData{
switch (self.childManager.requestType) {
case EWAPIRequestTypeGet:
APIRequest(Get)
break;
case EWAPIRequestTypePost:
APIRequest(Post)
break;
case EWAPIRequestTypeUploadImage:
APIRequest(PostImage)
break;
default:
break;
}
}
其中APIRequest()
是一个宏,该宏实现了网络请求:
/**
* 定义完成请求的宏
*/
#define APIRequest(requestType) \
{\
EW_WeakSelf\
[self startRequestAnimation];\开启动画
[self.apiRequest sendRequestBy##requestType##WithParams:self.childManager.paramsForAPI success:^(EWResponse *response) {\
[weakSelf cancellReqeustAnimation];\关闭动画
[weakSelf requestSuccess:response];\
} fail:^(EWResponse *response) {\
[weakSelf cancellReqeustAnimation];\关闭动画
[weakSelf requestFailed:response];\
}];\
}
在这里self.childManager.paramsForAPI
就将APIManager中的参数传递过去了.至于原理,在于将作为EWBaseAPIManager
的init方法重写了,当我们初始化子类TestAPIManager
的时候,父类EWBaseAPIManager
中的childManager
就已经成为了TestAPIManager
:
- (instancetype)init
{
self = [super init];
if (self) {
//初始化的时候,childManager即为当前的子类,完成请求参数的传递
if ([self conformsToProtocol:@protocol(EWAPIManagerProtocol)]) {
self.childManager = (NSObject *)self;
}
}
return self;
}
疑问4:底层如何进行数据请求?
这里我设计了一个分发请求的类EWAPIRequest
,里面目前只定义了三个方法:
/**
* get请求
*
* @param params 传入的参数,必须包含url,请求类型,参数,以及cache时间(没有就填@0)
* @param success 请求成功后的回调(请将请求成功后想做的事情写到这个block中)
* @param failure 请求失败后的回调(请将请求失败后想做的事情写到这个block中)
*/
- (NSURLSessionDataTask *)sendRequestByGetWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;
/**
* post请求
*
* @param params 传入的参数,必须包含url,请求类型,参数,以及cache时间(没有就填@0)
* @param success 请求成功后的回调(请将请求成功后想做的事情写到这个block中)
* @param failure 请求失败后的回调(请将请求失败后想做的事情写到这个block中)
*/
- (NSURLSessionDataTask *)sendRequestByPostWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;
/**
* 图片上传
*
* @param params 传入的参数,必须包含url,图片内容,图片key为EWUploadImageKey
* @param success 请求成功后的回调(请将请求成功后想做的事情写到这个block中)
* @param failure 请求失败后的回调(请将请求失败后想做的事情写到这个block中)
*/
- (NSURLSessionDataTask *)sendRequestByPostImageWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;
调用的地方在上方的请求宏中:
self.apiRequest sendRequestBy##requestType##WithParams:self.childManager.paramsForAPI success:^(EWResponse *response)
进入到EWAPIRequest
的方法实现中,我们可以看到里面是这样实现的:
NSURLSessionDataTask *dataTask = nil;
//通过工厂类获得请求的实例,实例必须遵循这个请求的协议
//这里采用硬编码的方式,决定到底是用什么库来进行网络请求,目的在于方便切换网络库
//KEWRequestByAFN表示采用AFN这个网络库请求数据
id requestInstance = [[EWRequestInstanceFactory shareInstance] requestInstance:KEWRequestByAFN];
//请求数据
dataTask = [requestInstance requestByGetWithParams:params success:^(id responseObject) {
//生成统一管理网络数据的response
//存入回调的数据
EWResponse *response = [[EWResponse alloc] initWithResopnseObject:responseObject andError:nil];
//回调这个response
success ? success(response) : nil;
} fail:^(NSError *error) {
//存入错误信息
EWResponse *errorResponse = [[EWResponse alloc] initWithResopnseObject:nil andError:error];
//回调这个response
failure ? failure(errorResponse) : nil;
SLog(@"请求失败-->%@",error);
}];
这里涉及到了几个协议和类,一一解释一下
EWNetworkRequestProtocol
:这个协议定义了最底层网络请求库需要遵守的方法,这里我用的AFN作为底层请求库.
KEWRequestByAFN
:这是个const常量,表示当前的请求库是基于AFN的
EWRequestInstanceFactory
:这是个工厂类,为了返回遵守EWNetworkRequestProtocol
协议的网络库的实例,底层是通过反射KEWRequestByAFN
这个字符串获得请求的实例,如果你要切换网络库,只需要新增一个请求类并且再定义一个const常量,在EWRequestInstanceFactory
中替换KEWRequestByAFN
即可.
EWResponse
:这个类的作用是用来统一保存请求的数据和错误信息
拿到请求的实例requestInstance
之后就调用EWNetworkRequestProtocol
中的requestByGetWithParams
方法来进行网络请求.之后就是将参数传入AFN请求类中实现最终的请求,并回调结果.
疑问5:回调结果的处理?
在EWAPIBaseManager
中,我用了两个私有方法在请求的宏里对回调结果进行转发,然后将结果回调给EWAPICallBackProtocol
协议中的方法:managerCallBackDidSuccess
,managerCallBackDidFailed
.
//回调成功的response,里面保存了请求成功的数据
- (void)requestSuccess:(EWResponse *)response{
//将response赋值给apiManager
self.response = response;
if ([self.delegate respondsToSelector:@selector(managerCallBackDidSuccess:)]) {
[self.delegate managerCallBackDidSuccess:self];
}
}
//回调失败的response,里面保存了请求失败的错误信息
- (void)requestFailed:(EWResponse *)response{
//将response赋值给apiManager
self.response = response;
if ([self.delegate respondsToSelector:@selector(managerCallBackDidFailed:)]) {
[self.delegate managerCallBackDidFailed:self];
}
}
就这样,就实现了整个网络请求的过程.至于这里为什么是回调response而不是直接回调responseObject,原因是用response可以统一管理回调数据和错误信息,就不需要再定义responseObject和error的变量了.
在这个库里面我还添加了加载动画,缓存,数据过滤等功能,有兴趣可以自己研究下,demo在这里.