[iOS]自己实现一个简单的离散化网络请求库

更新:梳理了库中的耦合文件,可以直接提取网络库文件夹进行使用,优化了缓存设计.
鸣谢:本人是在认真研读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在这里.

你可能感兴趣的:([iOS]自己实现一个简单的离散化网络请求库)