前言
在此所说的网络层架构,无非就是针对iOS网络请求的现状与问题,做出相应的解决方案。
iOS网络请求的现状与问题
- 网络接口规范化:所谓的规范、没有什么一定的规范,每个人都有每个人的规范,无破不立,也总会有不在规范之类的。虽然网络接口规范化能带来很多好处,可是开发中往往会遇到特例。很多时候,我们无法要求别人要怎样配合自己。那么唯有灵活多变,而又方便易用的网络框架被提到日程上来了。
- 网络请求处理过程重复啰嗦,而又惊人的相似。那么在网络请求过程处理上的复用就显得非常有必要。
- 取消网络请求麻烦,不能自动取消不必要的请求。那么也就需要对网络请求的取消做一些分装,提高一些所谓的性能。
这个版本的主要针对以上三个方面进行的架构,以后升级会往网络安全与性能优化方向靠。
技术层面的选择与看法
- 跟业务层对接的交互模式
跟业务层对接主要就Delegate,Notification,Block,KVO和Target-Action这几种方式吧;它们之间有好坏优劣我不想在此比较,可以自行查阅。我选择的方式是Block,没有其他原因,有且只有一个那就是Block简单粗暴,用起来方便、用起来爽在我看来很重要,所谓的那些问题其实都能够避免。
- 集约型和离散型API调用方式
先来解释一下离散型吧,“离散型API调用是这样的,一个API对应于一个APIManager”,听到这句话我就烦,我就pass掉了,一个API对应于一个APIManager。其实每个请求之间是多么惊人的相似,而用一个独立的Manager来管理,我有点无法接受。或许在自动取消这样的问题上有点优势吧。
网络层架构实现
AFNetworking基本是iOS网络请求的标配,各种优势不用言表,这个框架也是基于AFN针对上述问题与目标进行的二次封装架构。
架构思想
- 配置对象架构:由于本着灵活多变目标,我在几度考量之后决定使用配置对象的方式进行架构,如果对配置对象进行多层控制,相互组合能够非常灵活控制网络请求。再一个就是使用配置对象能够承载非常多的参数、而通常情况这些参数有基本相同,通过默认参数就可以解决掉相当一部分问题,遇到特殊情况再特殊配置,这样相当的简洁而又易于扩展。其实从这个方面来看,这种使用配置对象的方式起到了离散型API调用的作用,说白了也算是离散型API调用的一种变相。
- 多层控制架构:多层控制考虑更多的是对网络请求过程的复用,另一个就是这样多层组合灵活性更大,业务层有更大的发挥空间。
配置对象的实现
本次网络层架构主要依赖于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这个管理对象采用的是单例的设计模式,设计之初是为了避免麻烦的创建过程,但是随着框架在项目中的使用,我发现单例的设计模式限制了一定的灵活性,以后我一定会对此作出调整。为什么如此说,是因为虽然两层控制已经很灵活了,普遍适应了基本请求、特例请求这种模式。然而计划永远赶不上变化,如果基本请求形式有很多种呢?
自动取消的设计
设计思想
- 自动取消时机:自动取消,如何取消,什么情况取消?其实普遍来说当一个视图控制器销毁的时候,起生命周期内发送的任何网络请求只要没有着陆的都应该取消。
- 设计方案:为每一条请求设置一个自动取消操作的依赖对象,只要依赖对象销毁,那么我们就取消请求。有人说使用离散型API调用来实现要好,不需要设置依赖对象,我想说的是其实一样,离散型方式视图控制器就需要持有APIManager,并没有任何不简便之处。
- 实现步骤
- 包装一个能够取消网络请求的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中管理的全过程。