美文网首页iOS知识收集实用工具iOS日常须知
[iOS]自己实现一个简单的离散化网络请求库

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

作者: 未来行者 | 来源:发表于2017-03-05 13:21 被阅读706次

    更新:梳理了库中的耦合文件,可以直接提取网络库文件夹进行使用,优化了缓存设计.
    鸣谢:本人是在认真研读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<EWAPICallBackProtocol> delegate;
    
    //遵守协议的子类,须遵守EWAPIManagerProtocol协议
    @property (nonatomic , weak) NSObject<EWAPIManagerProtocol> *childManager;
    //自定义response用来统一保存数据和error
    @property (nonatomic , strong) EWResponse *response;
    
    //外部传入的参数
    @property (strong,nonatomic) NSMutableDictionary *outerParams;
    
    //是否需要动画
    @property (nonatomic , strong) NSDictionary *animationTargetAction;
    
    //数据过滤的方法,必须要遵守EWDataFilterProtocol协议
    
    - (id)filterDataWithFilter:(id<EWDataFilterProtocol>)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 <NSObject>
    @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<EWAPIManagerProtocol> *)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<EWNetworkRequestProtocol> 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在这里.

    相关文章

      网友评论

      • Teonardo:疑问1:如何设计这个APIManager?
        //遵守协议的子类,须遵守EWAPIManagerProtocol协议
        @property (nonatomic , strong) NSObject<EWAPIManagerProtocol> *childManager;
        这里应该声明为 weak 的吧
        未来行者:谢谢指出,这里确实是 weak,我自己用的时候也早就改过来了,可能文章忘了改,我去更新一下.
      • 阿不不不不:最近看casa的文章,demo拿了,谢谢啦
        未来行者:可以,他的文章还是不错的
      • dengxf:同一个接口,参数动态变化怎么扩展,比如上拉加载更多请求
        未来行者:baseManager可以加一个netxPage的方法做处理,同时协议中加一个index参数,这是我没考虑到的,谢谢提出
      • Calabash_Boy:又看了Casa的文章,这篇Demo实践的很是到位,学习了~

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

      本文链接:https://www.haomeiwen.com/subject/jiisgttx.html