iOS 网络层架构

前言

在此所说的网络层架构,无非就是针对iOS网络请求的现状与问题,做出相应的解决方案。

iOS网络请求的现状与问题

  1. 网络接口规范化:所谓的规范、没有什么一定的规范,每个人都有每个人的规范,无破不立,也总会有不在规范之类的。虽然网络接口规范化能带来很多好处,可是开发中往往会遇到特例。很多时候,我们无法要求别人要怎样配合自己。那么唯有灵活多变,而又方便易用的网络框架被提到日程上来了。
  2. 网络请求处理过程重复啰嗦,而又惊人的相似。那么在网络请求过程处理上的复用就显得非常有必要。
  3. 取消网络请求麻烦,不能自动取消不必要的请求。那么也就需要对网络请求的取消做一些分装,提高一些所谓的性能。

这个版本的主要针对以上三个方面进行的架构,以后升级会往网络安全与性能优化方向靠。

技术层面的选择与看法

  1. 跟业务层对接的交互模式

跟业务层对接主要就Delegate,Notification,Block,KVO和Target-Action这几种方式吧;它们之间有好坏优劣我不想在此比较,可以自行查阅。我选择的方式是Block,没有其他原因,有且只有一个那就是Block简单粗暴,用起来方便、用起来爽在我看来很重要,所谓的那些问题其实都能够避免。

  1. 集约型和离散型API调用方式
    先来解释一下离散型吧,“离散型API调用是这样的,一个API对应于一个APIManager”,听到这句话我就烦,我就pass掉了,一个API对应于一个APIManager。其实每个请求之间是多么惊人的相似,而用一个独立的Manager来管理,我有点无法接受。或许在自动取消这样的问题上有点优势吧。

网络层架构实现

AFNetworking基本是iOS网络请求的标配,各种优势不用言表,这个框架也是基于AFN针对上述问题与目标进行的二次封装架构。

架构思想

  1. 配置对象架构:由于本着灵活多变目标,我在几度考量之后决定使用配置对象的方式进行架构,如果对配置对象进行多层控制,相互组合能够非常灵活控制网络请求。再一个就是使用配置对象能够承载非常多的参数、而通常情况这些参数有基本相同,通过默认参数就可以解决掉相当一部分问题,遇到特殊情况再特殊配置,这样相当的简洁而又易于扩展。其实从这个方面来看,这种使用配置对象的方式起到了离散型API调用的作用,说白了也算是离散型API调用的一种变相。
  2. 多层控制架构:多层控制考虑更多的是对网络请求过程的复用,另一个就是这样多层组合灵活性更大,业务层有更大的发挥空间。

配置对象的实现

本次网络层架构主要依赖于QSPNetworkingConfig(网络控制)、QSPParameterConfig(请求空)、QSPLoadConfig(加载处理)、QSPErrorConfig(错误处理)这四个配置对象,下面分别介绍。

QSPNetworkingConfig配置对象的实现

QSPNetworkingConfig:这个配置对象用于配置网络层的参数,说白了就是表示一个AFHTTPSessionManager对象。目前抽象出如下参数进行控制,如后期有需要完全可以自由添加,扩展性非常高

/**
 超时时间(默认:15)
 */
@property (assign, nonatomic) NSTimeInterval timeoutInterval;

/**
 服务器地址
 */
@property (copy, nonatomic) NSString *basePath;

/**
 验证模式(默认:AFSSLPinningModeNone)
 */
@property (nonatomic, assign) AFSSLPinningMode SSLPinningMode;

/**
 本地绑定的证书(默认:nil)
 */
@property (nonatomic, strong) NSSet <NSData *> *pinnedCertificates;

/**
 是否允许无效证书(默认:NO)
 */
@property (nonatomic, assign) BOOL allowInvalidCertificates;

/**
 是否验证域名(默认:NO)
 */
@property (nonatomic, assign) BOOL validatesDomainName;

/**
 数据解析格式(默认:[NSSet setWithObjects:@"application/json",  @"text/json", @"text/javascript",@"text/html", @"text/plain", nil])
 */
@property (copy, nonatomic) NSSet<NSString *> *acceptableContentTypes;

/**
 请求头设置字典(默认:[NSDictionary dictionaryWithObject:@"ios" forKey:@"request-type"])
 */
@property (copy, nonatomic) NSDictionary<NSString *, NSString *> *HTTPHeaderDictionary;
QSPLoadConfig配置对象的实现

QSPLoadConfig:这个配置对象就是对网络请求过程进行控制,目前只有对只使用loadBegin、loadEnd两个block对开始请求和结束请求两个节点进行控制,非常简单在此略去。

QSPErrorConfig配置对象的实现

QSPErrorConfig:这个配置对象控制着网络请求的错误处理,这里把错误区分为网络请求错误与返回的数据错误两种形式如下:

/**
 网络错误处理(默认无)
 */
@property (copy, nonatomic) QSPNetworkingErrorBlock networkingErrorPrompt;

/**
 数据错误处理(默认无)
 */
@property (copy, nonatomic) QSPDataErrorBlock dataErrorPrompt;
QSPParameterConfig配置对象的实现

QSPParameterConfig:这个配置对象是对网络请求参数控制,不仅包含了网络请求所需要的各种参数,还包涵了上三个配置对象、这样可以为每条请求做出特定的控制,达到离散型API调用的效果。它的属性与工厂方法如下:

/**
 请求参数
 */
@property (copy, nonatomic) NSDictionary *parameters;

/**
 请求类型(默认为POST)
 */
@property (assign, nonatomic) QSPNetworkingType type;

/**
 上传文件数组(默认为nil)
 */
@property (copy, nonatomic) NSArray<QSPUploadModel *> *uploadModels;

/**
 请求api
 */
@property (copy, nonatomic) NSString *apiPath;

/**
 是否自动取消(默认为YES)
 */
@property (assign, nonatomic) BOOL autoCancel;

/**
 自动取消操作的依赖对象(此对象一销毁,就执行取消操作,所以autoCancel设置为YES的时候必须设置此参数自动取消才会起作用,默认为nil)
 */
@property (weak, nonatomic) id cancelDependence;

/**
 网络请求发生的控制器(默认为nil)
 */
@property (weak, nonatomic) UIViewController *controller;

/**
 是否处理错误(默认为YES)
 */
@property (assign, nonatomic) BOOL showError;

/**
 是否显示加载(默认为YES)
 */
@property (assign, nonatomic) BOOL showLoad;

/**
 是否执行成功的条件(默认为YES,如果为NO,只要网络请求成功就表示成功)
 */
@property (assign, nonatomic) BOOL executeConditionOfSuccess;

/**
 网络配置对象(默认为nil)
 */
@property (strong, nonatomic) QSPNetworkingConfig *networkingConfig;

/**
 错误配置对象(默认为nil)
 */
@property (strong, nonatomic) QSPErrorConfig *errorConfig;

/**
 加载配置对象(默认为nil)
 */
@property (strong, nonatomic) QSPLoadConfig *lodaConfig;

/**
 进度回调(默认为nil)
 */
@property (copy, nonatomic) QSPProgressBolck progress;

/**
 请求完成回调(默认为nil)
 */
@property (copy, nonatomic) QSPCompletionBlock completion;

+ (instancetype)parameterConfigWithParameters:(NSDictionary *)parameters apiPath:(NSString *)apiPath cancelDependence:(id)cancelDependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;
- (instancetype)initWithParameters:(NSDictionary *)parameters apiPath:(NSString *)apiPath cancelDependence:(id)cancelDependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;

核心逻辑实现

QSPNetworkingManager

QSPNetworkingManager:这是网络管理对象,使用QSPNetworkingConfig、QSPErrorConfig、QSPLoadConfig三个配置对象配置并控制着。然而每个请求的发送都会提供一个QSPNetworkingConfig配置对象这个对象有着更高的控制能力,这样就达到了两层控制,QSPNetworkingConfig其实为方便起见又用参数对某些地方进行控制,可以说有的具有三层控制。其头文件如下:

/**
 配置对象
 */
@property (strong, nonatomic) QSPNetworkingConfig *config;

/**
 错误配置对象
 */
@property (strong, nonatomic) QSPErrorConfig *errorConfig;

/**
 加载配置对象
 */
@property (strong, nonatomic) QSPLoadConfig *loadConfig;

/**
 成功的条件(如果不设置,只要网络请求成功,就认为是成功)
 */
@property (copy, nonatomic) QSPConditionOfSuccessBolck conditionOfSuccess;

/**
 配置框架

 @param networkingConfig 网络配置
 @param errorConfig 错误配置
 @param loadConfig 加载配置
 @param condictionOfSuccess 成功的条件(如果不设置,只要网络请求成功,就认为是成功)
 */
+ (void)configWithNetworkingConfig:(QSPNetworkingConfig *)networkingConfig errorConfig:(QSPErrorConfig *)errorConfig loadConfig:(QSPLoadConfig *)loadConfig condictionOfSuccess:(QSPConditionOfSuccessBolck)condictionOfSuccess;

/**
 单例方法
 */
+ (instancetype)manager;

/**
 默认调用(POST方式调用,最终转化为callWithParameterConfig方法调用)

 @param apiPath api
 @param parameters 参数
 @param dependence 依赖对象
 @param completion 完成的回调
 @return QSPNetworkingObject对象
 */
+ (QSPNetworkingObject *)defaultCall:(NSString *)apiPath parameters:(NSDictionary *)parameters cancelDependence:(id)dependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;

/**
 get调用(最终转化为callWithParameterConfig方法调用)

 @param apiPath api
 @param parameters 参数
 @param dependence 依赖对象
 @param completion 完成的回调
 @return QSPNetworkingObject对象
 */
+ (QSPNetworkingObject *)getCall:(NSString *)apiPath parameters:(NSDictionary *)parameters cancelDependence:(id)dependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;

/**
 使用配置对象调用

 @param parameterConfig 配置对象
 @return QSPNetworkingObject对象
 */
+ (QSPNetworkingObject *)callWithParameterConfig:(QSPParameterConfig *)parameterConfig;
QSPNetworkingConfig对应AFHTTPSessionManager的设置
- (AFHTTPSessionManager *)sessionManagerWithNetworkingConfig:(QSPNetworkingConfig *)config
{
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:config.basePath]];
    sessionManager.requestSerializer.timeoutInterval = config.timeoutInterval;
    sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];
    AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
    sessionManager.responseSerializer = serializer;
    [config.HTTPHeaderDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
        [sessionManager.requestSerializer setValue:key forHTTPHeaderField:obj];
    }];
    sessionManager.responseSerializer.acceptableContentTypes = config.acceptableContentTypes;
    sessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:config.SSLPinningMode];
    sessionManager.securityPolicy.allowInvalidCertificates = config.allowInvalidCertificates;//忽略https证书
    sessionManager.securityPolicy.validatesDomainName = config.validatesDomainName;//是否验证域名

    return sessionManager;
}
请求执行过程(以POST方式为例)
[sessionManager POST:parameterConfig.apiPath parameters:parameterConfig.parameters constructingBodyWithBlock:^(id <AFMultipartFormData> formData) {
        //上传文件
        for (QSPUploadModel *uploadModel in parameterConfig.uploadModels) {
            [formData appendPartWithFileData:uploadModel.data name:uploadModel.name fileName:uploadModel.fileName mimeType:uploadModel.mimeType];
        }
    } progress:^(NSProgress * _Nonnull uploadProgress) {
        //出来进度
        if (parameterConfig.progress) {
            parameterConfig.progress(uploadProgress);
        }
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //成功着陆
        [weakSelf susseccWithParameterConfig:parameterConfig task:task responseObject:responseObject];
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //失败着陆
        [weakSelf failureWithParameterConfig:parameterConfig task:task error:error];
    }];
请求成功着陆的执行过程
- (void)susseccWithParameterConfig:(QSPParameterConfig *)parameterConfig task:(NSURLSessionDataTask *)task responseObject:(id)responseObject
{
    //打印请求信息
    QSPNetworkingLog(@"\n接口地址:%@\n接口参数:%@\n上传文件:%@\n接口返回:%@", task.currentRequest.URL.absoluteString, parameterConfig.parameters, parameterConfig.uploadModels, responseObject);
    [self removeDependence:parameterConfig andTask:task];
    [self removeLoad:parameterConfig];

    //出来数据错误
    if (parameterConfig.showError) {
        //选择QSPErrorConfig,这里以parameterConfig的优先级高
        QSPErrorConfig *errorConfig = parameterConfig.errorConfig ? parameterConfig.errorConfig : self.errorConfig;
        if (errorConfig.dataErrorPrompt) {
            errorConfig.dataErrorPrompt(responseObject, parameterConfig.controller);
        }
    }

    //执行成功条件
    if (parameterConfig.executeConditionOfSuccess) {
        if (self.conditionOfSuccess) {
            if (self.conditionOfSuccess(responseObject)) {
                if (parameterConfig.completion) {
                    parameterConfig.completion(YES, responseObject, nil);
                }
            }
            else
            {
                if (parameterConfig.completion) {
                    parameterConfig.completion(NO, responseObject, nil);
                }
            }
        }
        else
        {
            if (parameterConfig.completion) {
                parameterConfig.completion(YES, responseObject, nil);
            }
        }
    }
    else
    {
        if (parameterConfig.completion) {
            parameterConfig.completion(YES, responseObject, nil);
        }
    }
}
对单例的设计方式进行说明

QSPNetworkingManager这个管理对象采用的是单例的设计模式,设计之初是为了避免麻烦的创建过程,但是随着框架在项目中的使用,我发现单例的设计模式限制了一定的灵活性,以后我一定会对此作出调整。为什么如此说,是因为虽然两层控制已经很灵活了,普遍适应了基本请求、特例请求这种模式。然而计划永远赶不上变化,如果基本请求形式有很多种呢?

自动取消的设计
设计思想
  1. 自动取消时机:自动取消,如何取消,什么情况取消?其实普遍来说当一个视图控制器销毁的时候,起生命周期内发送的任何网络请求只要没有着陆的都应该取消。
  2. 设计方案:为每一条请求设置一个自动取消操作的依赖对象,只要依赖对象销毁,那么我们就取消请求。有人说使用离散型API调用来实现要好,不需要设置依赖对象,我想说的是其实一样,离散型方式视图控制器就需要持有APIManager,并没有任何不简便之处。
  3. 实现步骤
    • 包装一个能够取消网络请求的QSPNetworkingObject对象,其头文件如下:
/**
 网络请求对象
 */
@interface QSPNetworkingObject : NSObject

@property (strong, nonatomic, readonly) NSURLSessionTask *task;

+ (instancetype)networkingObjectWithTask:(NSURLSessionTask *)task autoCancel:(BOOL)autoCancel;
- (instancetype)initWithTask:(NSURLSessionTask *)task autoCancel:(BOOL)autoCancel;

/**
 取消请求
 */
- (void)cancel;

@end
  • 动态给取消依赖对象添加能够取消请求的网络对象(如上步骤所述),这里使用的数组,然后再把网络对象加在数组中,因为存在多个请求依赖一个对象,具体如下:
        QSPNetworkingObject *obj = [QSPNetworkingObject networkingObjectWithTask:task autoCancel:(parameterConfig.autoCancel && parameterConfig.cancelDependence) ? YES : NO];

        if (parameterConfig.autoCancel && parameterConfig.cancelDependence) {
            NSMutableArray *networkingObjects = objc_getAssociatedObject(parameterConfig.cancelDependence, QSPNetworkingObject_arrayName);
            if (networkingObjects == nil) {
                networkingObjects = [NSMutableArray arrayWithCapacity:1];
                objc_setAssociatedObject(parameterConfig.cancelDependence, QSPNetworkingObject_arrayName, networkingObjects, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }

            if (obj) {
                [networkingObjects addObject:obj];
            }
        }
  • 取消网络请求,因为依赖对象销毁的时候,上步骤中添加包装取消网络请求的对象也随之销毁,所以在其dealloc方法中把网络请求取消掉。
- (void)dealloc
{
//    QSPNetworkingLog(@"%@取消啦---------------------------------------------------", self.task.currentRequest.URL.absoluteString);
    if (self.autoCancel) {
        [self cancel];
    }
}
- (void)cancel
{
    //    QSPNetworkingLog(@"---------------------------------------------------%@%zi", self.task.currentRequest.URL.absoluteString, self.task.state);
    if (self.task.state != NSURLSessionTaskStateCompleted && self.task.state != NSURLSessionTaskStateCanceling) {
        QSPNetworkingLog(@"---------------------------------------------------%@取消啦", self.task.currentRequest.URL.absoluteString);
        [self.task cancel];
    }
}
  • 把动态给依赖对象中添加对象移除掉,在每一条请求着陆后都移除,避免依赖对象持有太多的之前包装的具有取消网络请求能力的对象。

        NSMutableArray *networkingObjects = objc_getAssociatedObject(parameterConfig.cancelDependence, QSPNetworkingObject_arrayName);
        if (networkingObjects) {
            QSPNetworkingObject *netObj;
            for (QSPNetworkingObject *obj in networkingObjects) {
                if (obj.task == task) {
                    netObj = obj;
                    break;
                }
            }
            [networkingObjects removeObject:netObj];
        }

框架的使用

配置网络框架

此网络框架的配置需要QSPNetworkingConfig(网络配置对象)、QSPErrorConfig(错误处理配置对象)、QSPLoadConfig(加载处理配置对象)以及一个判断网络请求数据正确的block共同完成。

- (void)configNetworking {
    //创建网络配置对象,控制网络请求
    QSPNetworkingConfig *networkingConfig = [QSPNetworkingConfig networkingConfigWithBasePath:K_NetService_Base];

    //创建错误处理配置对象,处理网络、数据错误
    QSPErrorConfig *errorConfig = [QSPErrorConfig errorConfigWithNetworkingErrorPrompt:^(NSError *error, UIViewController *controller) {
        if (error.code == -999) {
            [LoadClass showMessage:K_NetRequestMessage_Cancel toView:self.window];
        }
        else if (error.code == -1001)
        {
            [LoadClass showMessage:K_NetRequestMessage_TimeOut toView:controller.view];
        }
        else
        {
            [LoadClass showMessage:K_NetRequestMessage_Failure toView:controller.view];
        }
    } dataErrorPrompt:^void(id responseObject, UIViewController *controller) {
        if (responseObject == nil || [responseObject isKindOfClass:[NSNull class]]) {
            [LoadClass showMessage:K_NetRequestMessage_NoData toView:controller.view];
        } else {
            if ([responseObject[@"status"] intValue] != 1) {
                NSString *message = responseObject[@"message"] ? responseObject[@"message"] : K_NetRequestMessage_Error;
                [LoadClass showMessage:message toView:controller.view];
            }
        }
    }];

    //创建加载处理配置对象,处理加载过程
    QSPLoadConfig *loadConfig = [QSPLoadConfig loadConfigWithLoadBegin:^(UIViewController *controller){
        [LoadClass beginLoadWithMessage:K_NetRequestMessage_Load toView:controller.view];
    } loadEnd:^(UIViewController *controller){
        [LoadClass endLoadFromView:controller.view];
    }];

    //配置网络框架
    [QSPNetworkingManager configWithNetworkingConfig:networkingConfig errorConfig:errorConfig loadConfig:loadConfig condictionOfSuccess:^BOOL(id responseObject) {
        return [responseObject[@"status"] integerValue] == 1;
    }];
}

发送请求

分装了如下三个方法来发送请求,分别为默认方式(post)、get方式、参数配置方式(可以通过配置对象配置各种适合自己的请求)。
+ (QSPNetworkingObject *)defaultCall:(NSString *)apiPath parameters:(NSDictionary *)parameters cancelDependence:(id)dependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;
+ (QSPNetworkingObject *)getCall:(NSString *)apiPath parameters:(NSDictionary *)parameters cancelDependence:(id)dependence controller:(UIViewController *)controller completion:(QSPCompletionBlock)completion;
+ (QSPNetworkingObject *)callWithParameterConfig:(QSPParameterConfig *)parameterConfig;
发送一个get请求

        [QSPNetworkingManager getCall:K_NetService_Regeo parameters:K_NetService_RegeoParamery cancelDependence:weakSelf controller:weakSelf completion:^(BOOL success, id responseObject, NSError *error) {
            if (success) {
                [LoadClass showMessage:@"请求成功!" toView:weakSelf.view];
            }
        }];
        [weakSelf.navigationController popViewControllerAnimated:NO];

最后当然是代码地址

代码下载

预告

下一篇记录下把这个框架加入到CocoaPods中管理的全过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值