美文网首页iOS那点事儿
iOS-AFNetworking网络层封装设计方案

iOS-AFNetworking网络层封装设计方案

作者: 宇玄丶 | 来源:发表于2017-04-19 10:20 被阅读613次

    前言

    网络层在项目开发中是不可缺少的一部分,网络层在一个App中承载了API调用,用户操作日志记录等任务。虽然苹果对网络请求部分已经做了很好的封装,但业界内最受欢迎的还是第三方库AFNetworking,很多工程师针对自己的项目对此做了各式各样的封装,我看了很多,每次接手项目的时候最关注的也是这一块。看到各位架构师各显神通展示了各种技巧,我为之感到兴奋,但兴奋之余,往往因为一些缺陷而感到失望。下面给大家介绍一下我目前使用的框架。

    在介绍之前,在此说明:本人技术有限,大家各持所需,哪怕有一个人看了之后有一点点收获,说明我做的事情就是有意义的,当然,哪里不好,有说错的地方,期望各位大神留言指正。

    一、封装的类

    封装的类

    先说说BaseAPIRequest这个类:
    BaseAPIRequest.h

    #import <Foundation/Foundation.h>
    #import "BaseAPIProxy.h"
    
    /** 首先是定义的四个协议 */
    @class BaseAPIRequest;
    /*---------------------API回调-----------------------*/
    @protocol APIManagerApiCallBackDelegate <NSObject>
    @required
    - (void)managerCallAPIDidSuccess:(BaseAPIRequest *)request;
    - (void)managerCallAPIDidFailed:(BaseAPIRequest *)request;
    @end
    
    /*---------------------API参数-----------------------*/
    @protocol APIManagerParamSourceDelegate <NSObject>
    @required
    - (NSDictionary *)paramsForApi:(BaseAPIRequest *)request;
    @optional
    - (void (^)(id <AFMultipartFormData> formData))uploadBlock:(BaseAPIRequest *)request;
    @end
    
    /**  以上两个协议部分,正如注释所示:三个代理方法,是每个界面必须要遵守写出来的。
          (1)需要上传的参数;
          (2)请求成功、失败之后的回调处理。
       (除非这个界面是分页请求,只需要写一个API参数的协议,这个后面再说。)
    */
    
    /**
     在此插一点说明:
     在iOS中有很多对象间数据的传递方式,大多数App在网络层所采用的方案主要集中于这三种:Delegate,Notification,Block。
     在这里,我主要以Delegate为主,Notification为辅。原因如下:
          (1)尽可能减少跨层数据交流的可能,限制耦合
       "跨层数据交流:【就是某一层(或模块)跟另外的与之没有直接对接关系的层(或模块)产生了数据交换。】"
          (2)统一回调方法,便于调试和维护
          (3)在跟业务层对接的部分只采用一种对接手段(delegate)限制灵活性,以此来交换应用的可维护性
     */
    
    /*---------------------API验证器-----------------------*/
    //验证器,用于验证API的返回或者调用API的参数是否正确
    /*
     使用场景:
     当我们确认一个api是否真正调用成功时,要看的不光是status,还有具体的数据内容是否为空。由于每个api中的内容对应的key都不一定一样,甚至于其数据结构也不一定一样,因此对每一个api的返回数据做判断是必要的,但又是难以组织的。
     为了解决这个问题,manager有一个自己的validator来做这些事情,一般情况下,manager的validator可以就是manager自身。
     
     1.有的时候可能多个api返回的数据内容的格式是一样的,那么他们就可以共用一个validator。
     2.有的时候api有修改,并导致了返回数据的改变。在以前要针对这个改变的数据来做验证,是需要在每一个接收api回调的地方都修改一下的。但是现在就可以只要在一个地方修改判断逻辑就可以了。
     3.有一种情况是manager调用api时使用的参数不一定是明文传递的,有可能是从某个变量或者跨越了好多层的对象中来获得参数,那么在调用api的最后一关会有一个参数验证,当参数不对时不访问api,同时自身的errorType将会变为CTAPIManagerErrorTypeParamsError。这个机制可以优化我们的app。
     4.特殊场景:如果用户会被要求填很多参数,这些参数都有一定的规则,比如邮箱地址或是手机号码等等,我们可以在validator里判断邮箱或者电话是否符合规则,比如描述是否超过十个字。从而manager在调用API之前可以验证这些参数,通过manager的回调函数告知上层controller。避免无效的API请求。加快响应速度,也可以多个manager共用.
     */
    @protocol APIManagerValidator <NSObject>
    @required
    /*
     所有的callback数据都应该在这个函数里面进行检查,事实上,到了回调delegate的函数里面是不需要再额外验证返回数据是否为空的。
     因为判断逻辑都在这里做掉了。
     而且本来判断返回数据是否正确的逻辑就应该交给manager去做,不要放到回调到controller的delegate方法里面去做。
     */
    - (BOOL)manager:(BaseAPIRequest *)request isCorrectWithCallBackData:(BaseResponse *)data;
    @end
    
    /*---------------------APIManager-----------------------*/
    
    @protocol APIManager <NSObject>
    @optional
    - (Class)responseClass;
    - (AFHTTPRequestSerializer <AFURLRequestSerialization> *)requestSerializer;
    - (AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer;
    - (NSDictionary *)reformParamsForApi:(NSDictionary *)params;
    - (void)reformData;
    @required
    
    /** 又来required,因为此设计为网络层的抽离,
    就是说把所有请求都抽离出来,
    在Controller里面只有上传的参数以及成功、失败回到的三个协议方法,
    这样一来,大大缩小了控制器里面的代码,
    处理业务逻辑变得更加清晰,维护起来也更加方便。
     */
    /** 请求的url */
    - (NSString *)requestPath;
    /** 请求类型,(GET、POST...)*/
    - (APIManagerRequestType)requestType;
    
    @end
    
    

    BaseAPIRequest类的声明

    @interface BaseAPIRequest : NSObject
    @property (nonatomic, weak) id<APIManagerApiCallBackDelegate> delegate;
    @property (nonatomic, weak) id<APIManagerParamSourceDelegate> paramSource;
    @property (nonatomic, weak) id<APIManagerValidator> validator;
    @property (nonatomic, weak) id<APIManager> child;
    
    /**  不做解释,看名称自己理解即可。*/
    @property (nonatomic, assign, readonly) BOOL isReachable;
    @property (nonatomic, strong) BaseResponse *responseData;
    @property (nonatomic, assign, readonly)APIManagerErrorType errorType;
    @property (nonatomic, copy) NSString *msg;
    @property (nonatomic, assign) BOOL disableErrorTip;
    
    - (instancetype)initWithDelegate:(id)delegate paramSource:(id)paramSource;
    
    /** 此处定义就是每次发起请求需要调用的方法 */
    - (NSInteger)loadDataWithHUDOnView:(UIView *)view;
    - (NSInteger)loadDataWithHUDOnView:(UIView *)view HUDMsg:(NSString *)HUDMsg;
    
    /** 取消所有请求,根据项目需要使用*/
    - (void)cancelAllRequests;
    - (void)cancelRequestWithRequestId:(NSInteger)requestID;
    @end
    

    BaseAPIRequest.m

    #import "BaseAPIRequest.h"
    #import "BaseAPIProxy.h"
    #import "LoginRequest.h"
    
    @interface BaseAPIRequest ()
    @property (nonatomic, copy, readwrite) NSString *errorMessage;
    @property (nonatomic, copy, readwrite) NSString *successMessage;
    @property (nonatomic, readwrite) APIManagerErrorType errorType;
    @property (nonatomic, strong) NSMutableArray *requestIdList;
    @property (nonatomic, strong)UIView *hudSuperView;
    @property (nonatomic, assign)NSInteger reloginCount;
    @end
    
    @implementation BaseAPIRequest
    
    /** 此处就是发起请求的调用方法,后文会举例说明 */
    #pragma mark - calling api
    - (NSInteger)loadDataWithHUDOnView:(UIView *)view {
        return [self loadDataWithHUDOnView:view HUDMsg:@""];
    }
    
    - (NSInteger)loadDataWithHUDOnView:(UIView *)view HUDMsg:(NSString *)HUDMsg {
        [self cancelAllRequests];
        if (view) {
            self.hudSuperView = view;
            [MBProgressHUD showLoadingHUD:HUDMsg onView:self.hudSuperView];
        }
        NSDictionary *params = [self.paramSource paramsForApi:self];
        if ([self.child respondsToSelector:@selector(reformParamsForApi:)]) {
            params = [self.child reformParamsForApi:params];
        }
        /**  
            由于考虑接口的安全性,此处为对参数加密处理,
            有则根据需求自行处理,如果接口没有特殊要求忽略即可。
        */
    //    params = [self signatureParams:params];
        NSInteger requestId = [self loadDataWithParams:params];
        return requestId;
    }
    
    - (NSInteger)loadDataWithParams:(NSDictionary *)params {
        NSInteger requestId = 0;
        if ([self isReachable]) {
            if ([self.child respondsToSelector:@selector(requestSerializer)]) {
                [BaseAPIProxy sharedInstance].requestSerializer = self.child.requestSerializer;
            } else {
    
    /** 关于此处:
        AFJSONRequestSerializer:接口已JSON的格式上传;
        AFHTTPRequestSerializer: 正常的参数拼接:“name/yuxuan (各种斜杠拼接的)
        根据自己需求,自行切换模式”
     */
                [BaseAPIProxy sharedInstance].requestSerializer = [AFJSONRequestSerializer serializer]; 
            }
            if ([self.child respondsToSelector:@selector(responseSerializer)]) {
                [BaseAPIProxy sharedInstance].responseSerializer = self.child.responseSerializer;
            } else {
                [BaseAPIProxy sharedInstance].responseSerializer = [AFJSONResponseSerializer serializer];
            }
    /** 
        此处就是load请求之后,调用BaseAPIProxy这个类,可以理解为对AF的封装(其中可以看到几个参数:
        * requestType:请求方式:GET、POST...
        * requestPath:url
      (看明白的同学应该想到,前面.h中有两个需要遵守的协议:)
          /** 请求的url */
          - (NSString *)requestPath;
          /** 请求类型,(GET、POST...)*/
          - (APIManagerRequestType)requestType;
       就是将参数拼接到请求当中。
    */
            [[BaseAPIProxy sharedInstance] callAPIWithRequestType:self.child.requestType params:params requestPath:self.child.requestPath uploadBlock:[self.paramSource respondsToSelector:@selector(uploadBlock:)]?[self.paramSource uploadBlock:self]:nil success:^(BaseAPIResponse *response) {
                [self successedOnCallingAPI:response];
            } fail:^(BaseAPIResponse *response) {
                [self failedOnCallingAPI:response withErrorType:response.errorType];
            }];
            [self.requestIdList addObject:@(requestId)];
            return requestId;
            
        } else {
            [self failedOnCallingAPI:nil withErrorType:APIManagerErrorTypeNoNetWork];
            return requestId;
        }
        return requestId;
    }
    

    请求成功、失败

    - (void)successedOnCallingAPI:(BaseAPIResponse *)response {
        if (self.hudSuperView) {
            [MBProgressHUD hideLoadingHUD];
        }
        [self removeRequestIdWithRequestID:response.requestId];
        
        DLog(@"%@:%@", [self.child requestPath],response.responseData);
        
        if ([self.child respondsToSelector:@selector(responseClass)]) {
            self.responseData =  [[self.child responseClass] yy_modelWithDictionary:response.responseData];
        /**  
            200:请求成功之后的状态码,
            如果是10000就改成10000,根据自己的接口返回自行处理
        */
            if (self.responseData.errcode!=200) {
                response.msg = self.responseData.msg;
                [self failedOnCallingAPI:response withErrorType:APIManagerErrorTypeDefault];
                return;
            }
        } else {
            self.responseData = response.responseData;
        }
        
        if ([self.validator respondsToSelector:@selector(manager:isCorrectWithCallBackData:)] && ![self.validator manager:self isCorrectWithCallBackData:self.responseData]) {
            [self failedOnCallingAPI:response withErrorType:APIManagerErrorTypeNoContent];
        } else {
            if ([self.child respondsToSelector:@selector(reformData)]) {
                [self.child reformData];
            }
            [self.delegate managerCallAPIDidSuccess:self];
        }
    }
    
    - (void)failedOnCallingAPI:(BaseAPIResponse *)response withErrorType:(APIManagerErrorType)errorType {
        if (self.hudSuperView) {
            [MBProgressHUD hideLoadingHUD];
        }
        
        self.errorType = errorType;
        self.msg = response.msg;
        [self removeRequestIdWithRequestID:response.requestId];
        switch (errorType) {
        /** 
            此处就是请求失败返回的 各种状态,根据需求,自行修改处理
         */
            case APIManagerErrorTypeDefault:
                self.errorMessage = response.msg;
                break;
            case APIManagerErrorTypeSuccess:
                break;
            case APIManagerErrorTypeNoContent:
                break;
            case APIManagerErrorTypeParamsError:
                break;
            case APIManagerErrorTypeTimeout:
                self.msg = Tip_RequestOutTime;
                break;
            case APIManagerErrorTypeNoNetWork:
                self.msg = Tip_NoNetwork;
                break;
            case APIManagerErrorLoginTimeout:
                self.msg = Tip_LoginTimeOut;
                break;
            default:
                break;
        }
        if (self.errorType==APIManagerErrorLoginTimeout) {
            if (!self.reloginCount && ![self isKindOfClass:[LoginRequest class]]) {
                self.reloginCount++;
                [LoginRequest autoReloginSuccess:^{
                    [self loadDataWithHUDOnView:self.hudSuperView];
                } failure:^{
                    [UserManager removeLocalUserLoginInfo];
                    [kAppDelegate loadLoginVC];
                }];
            } else {
                [UserManager removeLocalUserLoginInfo];
                [kAppDelegate loadLoginVC];
            }
        } else {
            [self.delegate managerCallAPIDidFailed:self];
            if (self.hudSuperView && !self.disableErrorTip) {
                [MBProgressHUD showMsgHUD:response.msg];
            }
        }
    }
    

    PS:以上就是发起请求之后的处理的步骤,细心的同学应该注意到了,这里成功、失败提到的是" BaseAPIResponse"这个类,所谓Response就是响应的意思,只有发起请求request之后,通过服务器响应response,才能判断请求是成功还是失败,所以响应单独抽个类处理。

    #pragma mark - private methods
    - (void)cancelAllRequests {
        [[BaseAPIProxy sharedInstance] cancelRequestWithRequestIDList:self.requestIdList];
        [self.requestIdList removeAllObjects];
    }
    
    - (void)cancelRequestWithRequestId:(NSInteger)requestID {
        [self removeRequestIdWithRequestID:requestID];
        [[BaseAPIProxy sharedInstance] cancelRequestWithRequestID:@(requestID)];
    }
    
    - (void)removeRequestIdWithRequestID:(NSInteger)requestId {
        NSNumber *requestIDToRemove = nil;
        for (NSNumber *storedRequestId in self.requestIdList) {
            if ([storedRequestId integerValue] == requestId) {
                requestIDToRemove = storedRequestId;
            }
        }
        if (requestIDToRemove) {
            [self.requestIdList removeObject:requestIDToRemove];
        }
    }
        /**
            参数的签名加密,上面提到过,
            根据自己的项目需求做处理,如果接口无特殊要求,
            请绕道自动忽略。
         */
    - (NSDictionary *)signatureParams:(NSDictionary *)params {
        if (![params isKindOfClass:[NSDictionary class]]) {
            return nil;
        }
        /** 处理签名方法 */
        、、、
        return newParams;
    }
    
    #pragma mark - getters and setters
    - (BOOL)isReachable {
        if ([AFNetworkReachabilityManager sharedManager].networkReachabilityStatus == AFNetworkReachabilityStatusUnknown) {
            return YES;
        } else {
            return [[AFNetworkReachabilityManager sharedManager] isReachable];
        }
    }
    
    - (NSMutableArray *)requestIdList {
        if (_requestIdList == nil) {
            _requestIdList = [[NSMutableArray alloc] init];
        }
        return _requestIdList;
    }
    @end
    

    下面说一下响应的处理,根据定义的变量名称,想必大家一目了然,就是失败的几种情况,状态码之类的。
    BaseAPIResponse.h

    #import <Foundation/Foundation.h>
    
    typedef NS_ENUM (NSUInteger, APIManagerErrorType){
        APIManagerErrorTypeDefault,       //没有产生过API请求,这个是manager的默认状态。
        APIManagerErrorTypeSuccess,       //API请求成功且返回数据正确,此时manager的数据是可以直接拿来使用的。
        APIManagerErrorTypeNoContent,     //API请求成功但返回数据不正确。如果回调数据验证函数返回值为NO,manager的状态就会是这个。
        APIManagerErrorTypeParamsError,   //参数错误,此时manager不会调用API,因为参数验证是在调用API之前做的。
        APIManagerErrorTypeTimeout,       //请求超时。ApiProxy设置的是20秒超时,具体超时时间的设置请自己去看ApiProxy的相关代码。
        APIManagerErrorTypeNoNetWork,     //网络不通。在调用API之前会判断一下当前网络是否通畅,这个也是在调用API之前验证的,和上面超时的状态是有区别的。
        APIManagerErrorLoginTimeout,       //登录超时
    };
    
    @interface BaseAPIResponse : NSObject
    
    @property (nonatomic, assign, readonly)NSInteger requestId;
    @property (nonatomic, copy)NSString *msg;
    @property (nonatomic, assign, readonly)APIManagerErrorType errorType;
    @property (nonatomic, assign, readonly)NSInteger httpStatusCode;
    @property (nonatomic, assign, readonly)id responseData;
    
    - (instancetype)initWithRequestId:(NSNumber *)requestId responseObject:(id)responseObject urlResponse:(NSHTTPURLResponse *)urlResponse;
    - (instancetype)initWithRequestId:(NSNumber *)requestId urlResponse:(NSHTTPURLResponse *)urlResponse error:(NSError *)error;
    
    @end
    
    /* 根据服务器返回数据结构设计基本数据,如状态码、提示信息等*/
    @interface BaseResponse : NSObject
    @property(nonatomic, assign) NSInteger errcode;
    @property(nonatomic, copy) NSString *msg;
    @end
    
        /** 
          这里是针对分页请求做的处理,
          前文提到,次方案分页请求是单独抽出来一个类来做处理的,
          并且发起请求的时候,只需要遵守BaseAPIRequest中三个必须实现的
          三个(上传的参数、请求成功、失败的回调)协议中的
          一个(上传的参数)即可,因为,内部已做处理。
     */
    @interface PageModel : NSObject
    @property(nonatomic,copy)NSArray *list;
    @property(nonatomic,copy)NSNumber *totalPage;
    @property(nonatomic,copy)NSNumber *totalRow;
    @property(nonatomic,copy)NSNumber *total_count;
    @end
    
    #import "BaseAPIResponse.h"
    
    @interface BaseAPIResponse ()
    @property (nonatomic, assign, readwrite)NSInteger httpStatusCode;
    @property (nonatomic, assign, readwrite)APIManagerErrorType errorType;
    @property (nonatomic, assign, readwrite)id responseData;
    @end
    
    @implementation BaseAPIResponse
    
    - (instancetype)initWithRequestId:(NSNumber *)requestId responseObject:(id)responseObject urlResponse:(NSHTTPURLResponse *)urlResponse {
        self = [super init];
        if (self) {
            self.httpStatusCode = urlResponse.statusCode;
            self.responseData = responseObject;
        }
        return self;
    }
    
    - (instancetype)initWithRequestId:(NSNumber *)requestId urlResponse:(NSHTTPURLResponse *)urlResponse error:(NSError *)error {
        self = [super init];
        if (self) {
            self.httpStatusCode = urlResponse.statusCode;
            self.msg = [self handelError:error];
            if (self.httpStatusCode==11010) {
                self.errorType = APIManagerErrorLoginTimeout;
            }
        }
        return self;
    }
    
    - (NSString *)handelError:(NSError*)error {
        NSString *errorMsg = Tip_RequestError;
        if (error) {
            NSData *responseData = error.userInfo[@"com.alamofire.serialization.response.error.data"];
            if (responseData) {
                NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:NULL];
                if ([response isKindOfClass:[NSDictionary class]]) {
                    errorMsg = safeString(response[@"msg"]);
                    return errorMsg;
                }
            }
            if (safeNumber(error.userInfo[@"_kCFStreamErrorCodeKey"]).integerValue==-2102) {
                errorMsg = Tip_RequestOutTime;
            }
        }
        return errorMsg;
    }
    
    @end
    
    @implementation BaseResponse
    
    
    @end
    
     /**
          因为解析用的是YYModel来处理的,个人推荐,可以看看,简单实用,很多都会帮你处理好,免去了解析的痛苦。
    */
    @implementation PageModel
    - (id)copyWithZone:(NSZone *)zone {
        return [self yy_modelCopy];
    }
    @end
    

    到这里,两个主要的类已经说完了,我们很快就要成功了,大家 加油!下面说说BaseAPIProxy这个类:

    #import <Foundation/Foundation.h>
    #import "BaseAPIResponse.h"
    
    typedef NS_ENUM (NSUInteger, APIManagerRequestType){
        APIManagerRequestTypeGet,
        APIManagerRequestTypePost,
        APIManagerRequestTypeUpload,
        APIManagerRequestTypeDelete,
        APIManagerRequestTypePut
    };
    
    typedef void(^APICallback)(BaseAPIResponse *response);
    
    @interface BaseAPIProxy : NSObject
    
    @property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
    @property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
    /**  来个单例*/
    + (instancetype)sharedInstance;
    
    /** 
        这里就是在文章开始说的那个BaseAPIRequest类中提到,
        发起请求之后,调用这个类,忘了的同学请上翻。⬆️
        上文中也提到此处可以理解为对AF的封装,
        每次都写请求多麻烦啊,对吧?并且是GET还是POST的啊(所以,上面列举了一堆枚举。)?
        这里都做了判断,没有说的东西。
    */
    - (NSInteger)callAPIWithRequestType:(APIManagerRequestType)requestType params:(NSDictionary *)params requestPath:(NSString *)requestPath uploadBlock:(void (^)(id <AFMultipartFormData> formData))uploadBlock success:(APICallback)success fail:(APICallback)fail;
    
    - (void)cancelRequestWithRequestID:(NSNumber *)requestID;
    - (void)cancelRequestWithRequestIDList:(NSArray *)requestIDList;
    
    @end
    

    BaseAPIProxy.m

    #import "BaseAPIProxy.h"
    
    #define kCookie @"Cookie"
    
    @interface BaseAPIProxy ()
    @property (nonatomic, strong) NSMutableDictionary *dispatchTable;
    @property (nonatomic, strong) NSNumber *recordedRequestId;
    @property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
    @end
    
    @implementation BaseAPIProxy
    
    + (instancetype)sharedInstance {
        static dispatch_once_t onceToken;
        static BaseAPIProxy *sharedInstance = nil;
        dispatch_once(&onceToken, ^{
            sharedInstance = [[BaseAPIProxy alloc] init];
        });
        return sharedInstance;
    }
    
    - (NSInteger)callAPIWithRequestType:(APIManagerRequestType)requestType params:(NSDictionary *)params requestPath:(NSString *)requestPath uploadBlock:(void (^)(id <AFMultipartFormData> formData))uploadBlock success:(APICallback)success fail:(APICallback)fail {
        NSString *urlString = [NSString stringWithFormat:@"%@%@",BaseUrl,requestPath];
        NSNumber *requestId = [self callApi:urlString requestType:requestType params:params uploadBlock:uploadBlock success:success fail:fail];
        return [requestId integerValue];
    }
    
    - (void)cancelRequestWithRequestID:(NSNumber *)requestID {
        NSURLSessionDataTask *dataTask = self.dispatchTable[requestID];
        [dataTask cancel];
        [self.dispatchTable removeObjectForKey:requestID];
    }
    
    - (void)cancelRequestWithRequestIDList:(NSArray *)requestIDList {
        for (NSNumber *requestId in requestIDList) {
            [self cancelRequestWithRequestID:requestId];
        }
    }
    /** 这个函数存在的意义在于,如果将来要把AFNetworking换掉,只要修改这个函数的实现即可。 */
    - (NSNumber *)callApi:(NSString *)URLString requestType:(APIManagerRequestType)requestType params:(NSDictionary *)params uploadBlock:(void (^)(id <AFMultipartFormData> formData))uploadBlock success:(APICallback)success fail:(APICallback)fail {
        // 之所以不用getter,是因为如果放到getter里面的话,每次调用self.recordedRequestId的时候值就都变了,违背了getter的初衷
        NSNumber *requestId = [self generateRequestId];
        
        self.sessionManager.requestSerializer = self.requestSerializer;
        self.sessionManager.requestSerializer.timeoutInterval = 10;
        self.sessionManager.responseSerializer = self.responseSerializer;
        [self setCookie];
        // 跑到这里的block的时候,就已经是主线程了。
        NSURLSessionDataTask *dataTask;
        switch (requestType)
        {
            case APIManagerRequestTypeGet:
            {
                dataTask = [self.sessionManager GET:URLString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
                } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    [self handelFailRequest:requestId task:task error:error fail:fail];
                }];
            }
                break;
            case APIManagerRequestTypePost:
            {
                dataTask = [self.sessionManager POST:URLString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
                } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    [self handelFailRequest:requestId task:task error:error fail:fail];
                }];
            }
                break;
            case APIManagerRequestTypeUpload:
            {
                self.sessionManager.requestSerializer.timeoutInterval = 20;
                dataTask = [self.sessionManager POST:URLString parameters:params constructingBodyWithBlock:uploadBlock progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
                } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    [self handelFailRequest:requestId task:task error:error fail:fail];
                }];
            }
                break;
            case APIManagerRequestTypeDelete:
            {
                dataTask = [self.sessionManager DELETE:URLString parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
                } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    [self handelFailRequest:requestId task:task error:error fail:fail];
                }];
            }
                break;
            case APIManagerRequestTypePut:
            {
                dataTask = [self.sessionManager PUT:URLString parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    [self handelSuccessRequst:requestId task:task responseObject:responseObject success:success];
                } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    [self handelFailRequest:requestId task:task error:error fail:fail];
                }];
            }
                break;
            default:
                break;
        }
        
        self.dispatchTable[requestId] = dataTask;
        return requestId;
    }
    
    - (void)handelSuccessRequst:(NSNumber *)requestId task:(NSURLSessionDataTask *)task responseObject:(id)responseObject success:(APICallback)success {
        NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]];
        //存储归档后的cookie
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
        [userDefaults setObject:cookiesData forKey:kCookie];
        [self setCookie];
        
        NSURLSessionDataTask *storedTask = self.dispatchTable[requestId];
        if (storedTask == nil) {
            // 如果这个task是被cancel的,那就不用处理回调了。
            return;
        } else {
            [self.dispatchTable removeObjectForKey:requestId];
        }
        
        BaseAPIResponse *response = [[BaseAPIResponse alloc] initWithRequestId:requestId responseObject:responseObject urlResponse:(NSHTTPURLResponse *)task.response];
        
        success?success(response):nil;
    }
    
    - (void)handelFailRequest:(NSNumber *)requestId task:(NSURLSessionDataTask *)task error:(NSError *)error fail:(APICallback)fail {
        NSURLSessionDataTask *storedTask = self.dispatchTable[requestId];
        if (storedTask == nil) {
            // 如果这个task是被cancel的,那就不用处理回调了。
            return;
        } else {
            [self.dispatchTable removeObjectForKey:requestId];
        }
        
        BaseAPIResponse *response = [[BaseAPIResponse alloc] initWithRequestId:requestId urlResponse:(NSHTTPURLResponse *)task.response error:error];
        
        fail?fail(response):nil;
    }
    
    

    并且对于请求成功之后也做了cookies处理

    - (NSNumber *)generateRequestId {
        if (_recordedRequestId == nil) {
            _recordedRequestId = @(1);
        } else {
            if ([_recordedRequestId integerValue] == NSIntegerMax) {
                _recordedRequestId = @(1);
            } else {
                _recordedRequestId = @([_recordedRequestId integerValue] + 1);
            }
        }
        return _recordedRequestId;
    }
    
    - (void)setCookie {
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
        if (![userDefaults objectForKey:kCookie]) {
            return;
        }
        //对取出的cookie进行反归档处理
        NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData:[userDefaults objectForKey:kCookie]];
        
        if (cookies) {
            //设置cookie
            NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
            for (id cookie in cookies) {
                [cookieStorage setCookie:(NSHTTPCookie *)cookie];
            }
        }
    }
    
    #pragma mark - getters and setters
    - (NSMutableDictionary *)dispatchTable {
        if (_dispatchTable == nil) {
            _dispatchTable = [[NSMutableDictionary alloc] init];
        }
        return _dispatchTable;
    }
    
    - (AFHTTPSessionManager *)sessionManager {
        if (_sessionManager == nil) {
            _sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:nil];
        }
        return _sessionManager;
    }
    
    - (NSString *)cookie {
        return safeString([[NSUserDefaults standardUserDefaults] valueForKey:kCookie]);
    }
    @end
    

    PS:以上除了分页请求就都介绍完了,但是革命尚未成功,同志而需努力!还差个分页,大家 加油!
    在此,我就不做过多的解释了,注释很清楚。

    #import "BaseAPIRequest.h"
    
    @protocol PageDelegate <NSObject>
    @required
    - (NSArray *)buildPageArray;
    @end
    
    @interface PageAPIRequest : BaseAPIRequest <APIManager>
    
    /**
     *  当前页数
     */
    @property (nonatomic, assign) NSUInteger                currentPage;
    /**
     *  最终结果
     */
    @property (nonatomic, strong) NSMutableArray*           listArray;
    /**
     *  是否还有数据,只要有数据返回,就认为还有下一页
     */
    @property (nonatomic, assign) BOOL                      moreData;
    
    /**
     *  清空listArray,currentPage = 1
     */
    - (void)reload;
    
    /**
     *  清空listArray,currentPage = 1
     */
    - (void)reloadOnView:(UIView *)view;
    
    /**
     *  每页多少条数据,默认20
     */
    - (NSUInteger)pageSize;
    
    @end
    
    #import "PageAPIRequest.h"
    
    @implementation PageAPIRequest
    
    - (id)init {
        self = [super init];
        if (self) {
            self.currentPage = 1;
        }
        return self;
    }
    
    - (id)initWithDelegate:(id)delegate paramSource:(id)paramSource {
        self = [super initWithDelegate:delegate paramSource:paramSource];
        if (self) {
            self.currentPage = 1;
        }
        return self;
    }
    
    - (NSUInteger)pageSize {
        return 10;
    }
    
    /** 这里呢,就是根据自己的接口来定义分页,看接口是那个字段,做修改即可。原理就是根据key会自取。*/
    - (NSDictionary *)reformParamsForApi:(NSDictionary *)params {
        NSMutableDictionary *newParmas = params?[params mutableCopy]:[NSMutableDictionary dictionary];
        [newParmas setObject:[NSNumber numberWithInteger:self.currentPage] forKey:@"page"];
        [newParmas setObject:[NSNumber numberWithInteger:[self pageSize]] forKey:@"pageSize"];
        、、、
        [newParmas setObject:[NSNumber numberWithInteger:[self pageSize]*(self.currentPage-1)] forKey:@"page_start"];
        return newParmas;
    }
    
    - (void)reformData {
        if (_currentPage == 1) {
            [self.listArray removeAllObjects];
        }
        
        NSArray *array = nil;
        if ([self.responseData respondsToSelector:@selector(buildPageArray)]) {
            array = [self.responseData performSelector:@selector(buildPageArray)];
            
        } else if ([self.responseData isKindOfClass:[NSArray class]]) {
            array = (NSArray *)self.responseData;
        }
        self.moreData = NO;
        if ([array count] > 0) {
            self.moreData = [array count] >= [self pageSize] ? YES : NO;
            self.currentPage ++;
            [self.listArray addObjectsFromArray:array];
        }
    }
    
    - (void)reload {
        self.currentPage = 1;
        [self loadDataWithHUDOnView:nil];
    }
    
    - (void)reloadOnView:(UIView *)view {
        self.currentPage = 1;
        [self loadDataWithHUDOnView:view];
    }
    
    - (NSString *)requestPath {
        return [self.child requestPath];
    }
    
    - (APIManagerRequestType)requestType {
        return [self.child requestType];
    }
    
    - (NSMutableArray *)listArray {
        if (!_listArray) {
            _listArray = [NSMutableArray array];
        }
        return _listArray;
    }
    @end
    

    PS:好了,大功告成,到这里,对于此方案的设计就全部讲解完毕,还记得如何使用吗?记住:每个界面只需要实现三个代理方法即可,当分页的时候,只需要一个即可。

    --- 下面举两个例子---

    因为文章中提到,次方案的设计是网络层的抽离,所以,每个请求都需要单独抽一个类出来:

    如果有这样的一个接口:
    {
    "errcode": 200,
    "msg": "操作成功",
    "data": [
    {
    "id": "1",
    "name": "1231",
    "price": "123",
    "img_url": "123"
    },
    {
    "id": "2",
    "name": "123",
    "price": "123",
    "img_url": "123"
    },
    ]
    }

    这是一个简单的商品列表,没有分页,当我们创建请求类的时候继承的是BaseAPIRequest这个类。

    BaseAPIRequest.h

    #import "BaseAPIRequest.h"
    /**
      创建请求类的时候继承BaseAPIRequest这个类,
      并且遵守<APIManager>协议,为什么?因为我们要实现三个协议方法啊。
      千万不要忘了!!!
    */
    /** 请求 */
    @interface GoodsListRequest : BaseAPIRequest <APIManager>
    
    @end
    
    /** 响应*/
    
    /**
        因为我们需要对请求成功之后,服务器返回的数据进行赋值,
        所以才需要创建响应方法,
        如果这个接口只是上传几个参数(比如修改密码,成功了就是成功了,不需要返回什么),
        那么我们无需再写Response的响应处理了,
        (说白了,就是就是下面的这个方法就不用写了。)
     */
    @interface GoodsListResponse : BaseResponse
    /** 
        为什么定义数组呢?因为服务器返回的就是数组啊,
        如果返回的是对象,那么此处就定义一个model呗。
    */
    @property(nonatomic,strong)NSArray *data;
    // @property(nonatomic,strong)UserModel *data;
    @end
    

    BaseAPIRequest.m

    #import "GoodsListRequest.h"
    
    @implementation GoodsListRequest
    /** 
        此处就是请求的接口,我的处理方式是定义一个.h类,
        专门放接口的,好修改。
    
        //商品列表
        #define Goods_List_Url                  @"/mall/shopList" 
    
        这里为啥只有一小段接口,前面的都是一样的,已经封装在BaseAPIProxy类中,
        忘了的同学请上翻⤴️。
    */
    - (NSString *)requestPath {
        return Goods_List_Url;
    }
    /** 你是什么请求方式,get、post..,根据接口,自行修改。*/
    - (APIManagerRequestType)requestType {
        return APIManagerRequestTypePost;
    }
    /** 
        请求成功的响应类,
        因为需要对请求成功之后的数据做处理,所有才会有这个类,
        名字对应好即可。
        在.h中,我有做了说明,
        如果这个接口只是上传几个参数,
        那么我们无需再写Response的响应处理了,
        将响应的类改为BaseResponse即可。
    */
    - (Class)responseClass {
        return [GoodsListResponse class];
    }
    @end
    
    @implementation GoodsListResponse
    
    /** 这里我用的是YYModel解析,这是YYModel的方法,*/
    
    YYModel使用起来还是很简单的,免去了很多解析的时间,
    想必大部分人都知道,要么就是MJExtension,使用方法差不多类似。看看就会用了。
    
    + (NSDictionary *)modelContainerPropertyGenericClass {
        return @{@"data" : [GoodsModel class]};
    }
    @end
    

    PS:好了,到这里,一个接口的请求就写完了,那么在Controller里面如何实现?直接上代码:

    1、首页你得把这个方法初始化吧,要不鬼知道你这是哪里来的逗比请求。

     // 1>导入头文件 ,定义请求方法
    @property(nonatomic,strong)GoodsListRequest *goodsListRequest;
     // 2>初始化
    - (GoodsListRequest *)goodsListRequest {
        if (!_goodsListRequest) {
    /** 
        注意:这里并不是简单的init初始化,你是需要遵守三个协议方法的,所以这里有两个self,因为文章开头我就讲了,忘了的同学请上翻⬆️*/
            _goodsListRequest = [[GoodsListRequest alloc] initWithDelegate:self paramSource:self];
        }
        return _goodsListRequest;
    }
    

    2、发起请求

    /**  
          这里self.contentView就是加载的小菊花显示在哪个视图上,
          因为我这里最底层的视图是自定义的contentView,根据需求、用户体验,可自行修改视图。
        */
    [self.goodsListRequest loadDataWithHUDOnView:self.contentView];
    

    3、遵守三个协议,忘了的同学请继续上翻⬆️,我已经说明好几次了,应该都能记住了,不管如何实现能否掌握了解,但只要写好请求,实现三个协议方法,基本就能成功了。

    #pragma mark - APIManagerParamSourceDelegate
    - (NSDictionary *)paramsForApi:(BaseAPIRequest *)request {
    /** 如果请求的接口需要传参,那么在此return。 */
        if (request == self.addCartRequest) {
            return @{@"goods_id":self.operatingGoods.goods_id,
                     @"number":@"1"};
        }
    /** 文章中已经提到,如果该请求接口没有参数,直接返回nil即可。 */
        return nil;
    }
    
    /** 请求之后的,成功、失败的回调。 */
    #pragma mark - APIManagerApiCallBackDelegate
    - (void)managerCallAPIDidSuccess:(BaseAPIRequest *)request {
        if (request == self.goodsListRequest) {
    /** 
        此处就是赋值操作,将请求下来的数据源,
        赋值给tableView的数据源,然后刷新表格即可。
     */
            self.shopTableView.dataArray = [[request.responseData valueForKey:@"data"] mutableCopy];
    
    /**
        注意request.responseData接收的数据格式,因为此接口返回的是数组,所以这么写。
        如果返回的是对象,很简单:
    
    /**  随便举个例子*/
        * self.userData:模型model 
                  (将返回的对象中的数据赋值给model,然后取model中的各个字段,赋值即可。
                    当然有表格的界面,在请求成功之后记得刷新表格。)
    
        * data : 此处的data为定义的model。
              文章中那个商品列表的请求提到,如果返回的是数组就定义数组,
              如果是对象就定义model。
          self.userData = [request.responseData valueForKey:@"data"];
    */
            [self.shopTableView reloadData];
            return;
        }
    /** 此处对应请求的API接口,成功之后的回调*/
        if (request == self.addCartRequest) {
            [MBProgressHUD showMsgHUD:@"加入购物车成功"];
            return;
        }
    }
    /** 失败,可以不写什么,因为返回的是return,根据需求自行处理吧。 */
    - (void)managerCallAPIDidFailed:(BaseAPIRequest *)request {
    }
    

    以上就是普通的接口请求,如果此接口为分页请求,文章中已经提到,只需要遵守一个参数的协议(APIManagerParamSourceDelegate)即可,这里就不在累赘了。

    PS:写了一下午,终于是搞完了,到这里,所有功能已全部讲完,有错误的地方,请大神指正。个人认为此框架使用起来还是超级简单的,免去了Controler里面大量的代码,留给更多的空间来处理逻辑。也希望小小的一篇文章能帮助更多的人,一起努力,未来、我们都会是全栈工程师。大家加油!

    代码:https://github.com/Baiyongyu/iOS-AFNetworking-.git

    相关文章

      网友评论

        本文标题:iOS-AFNetworking网络层封装设计方案

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