我们是基于AFNetWorking封装的,我们先简单说下AFNetWorking和NSUrlSession做的事:
请求网络是由NSUrlSession来做的,它内部维护了一个线程池,用来做网络请求。它调度线程,基于底层的CFSocket去发送请求和接受数据,这些线程都是并发的。
我们一开始初始化sessionManager的时候,一般都在主线程
当我们去调用get或post去请求数据,接着会进行request拼接,AF代理的字典映射,progress的KVO添加等等,到NSUrlSession的resume之前的准备工作,仍旧是在主线程中的
然后我们调用NSUrlSession的resume,接着跑到NSUrlSession内部去对网络进行数据请求了,在它内部是多线程并发的请求数据
数据请求完成后,回调回来再我们一开始生成的并发数为1的NSOperationQueue中,这个时候回事多线程串行的回调回来的
然后我们创建并发的多线程,去对这些数据进行各种解析
最后我们如果有自定义的completionQueue,则在自定义的queue中回调回来,也就是分线程回调回来,否则就是主队列,主线程中回调结束。
所以从苹果推行NSUrlSession后,你基本可以自己基于NSUrlSession来封装自己的网络库了,因为AFNetWorking毕竟是通用型的不是完全适用你公司业务的,当然这个前提是大公司有人力物力来做这个。
接下来看下我们做了什么:
我们主要有三个类:LPDBRequestObject,LPDBHttpManager和LPDBModel,我们从我们怎么调用反过来解释这三个类。
我们发起一个请求声明一个request继承LPDBRequestObject,
#import
#import "LPDBRequestObject.h"
@interface KZWRequestServerstatus : LPDBRequestObject
@end
#import "KZWRequestServerstatus.h"
@implementation KZWRequestServerstatus
- (instancetype)init{
if (self = [super init]) {
self.path = @"app/serverstatus";
}
return self;
}
@end
这就是一个请求,我们来看下调用:
- (void)serverStatus {
KZWRequestServerstatus *requestServerstatus = [KZWRequestServerstatus new];
[requestServerstatus startRequestComplete:^(id object, NSError *error) {
if (error.code == 500) {
self.netImage.image = self.bgImage;
self.netLabel.text = @"服务器维护中,请刷新重试";
self.hidden = NO;
}
}];
}
好,先我们来看下LPDBRequestObject里有什么在干嘛:
#import
#import
#import "LPDBHttpManager.h"
typedef NS_ENUM(NSUInteger, LPDHTTPMethod) {
LPDHTTPMethodGet,
LPDHTTPMethodPost,
LPDHTTPMethodPut,
LPDHTTPMethodDelete,
};
typedef void (^LPDBRequestComplete)(id object, NSError *error);
@interface LPDBRequestObject : MTLModel
@property (nonatomic, strong) NSString *path;
/**
* json 解析成的对象类名 当josn是数组时 表示数组对象的类名
*/
@property (nonatomic, strong) NSString *className;
@property (nonatomic, strong) NSArray *images;
@property (nonatomic, strong) NSDictionary *paramDic;
@property (nonatomic, assign) LPDHTTPMethod method;
@property (nonatomic, readonly) NSURLSessionDataTask *task;
- (void)startRequestComplete:(LPDBRequestComplete)complete;
- (void)startRequestComplete:(LPDBRequestComplete)complete progress:(void (^)(NSProgress * uploadProgress)) progress;
- (void)cancel;
@end
#import "LPDBRequestObject.h"
#import "NSObject+Dictionary.h"
#import
#import "NSError+LPDErrorMessage.h"
#import "ELMKeychainUtil.h"
#import "UIApplication+ELMFoundation.h"
#import "KZWConstants.h"
@interface LPDBRequestObject ()
@property (nonatomic, copy) LPDBRequestComplete complete;
@end
@implementation LPDBRequestObject
- (void)startRequestComplete:(LPDBRequestComplete)complete
progress:(nullable void (^)(NSProgress *_Nonnull uploadProgress))progress {
self.complete = complete;
switch (self.method) {
case LPDHTTPMethodGet: {
_task = [LPDBHttpManager GET:self.path
parameters:self.params
completionHandler:^(NSURLSessionDataTask *_Nonnull task, id _Nonnull responseObject, NSError *_Nonnull error) {
[self handleInMainThread:task responseObject:responseObject error:error];
}];
} break;
case LPDHTTPMethodPut: {
_task = [LPDBHttpManager PUT:self.path
parameters:self.params
completionHandler:^(NSURLSessionDataTask *_Nonnull task, id _Nonnull responseObject, NSError *_Nonnull error) {
[self handleInMainThread:task responseObject:responseObject error:error];
}];
} break;
case LPDHTTPMethodPost: {
_task = [LPDBHttpManager POST:self.path
parameters:self.params
images:self.images
completionHandler:^(NSURLSessionDataTask *_Nonnull task, id _Nonnull responseObject, NSError *_Nonnull error) {
[self handleInMainThread:task responseObject:responseObject error:error];
}
progress:^(NSProgress *_Nonnull uploadProgress) {
if (progress) {
progress(uploadProgress);
}
}];
} break;
case LPDHTTPMethodDelete: {
_task = [LPDBHttpManager DELETE:self.path
parameters:self.params
completionHandler:^(NSURLSessionDataTask *_Nonnull task, id _Nonnull responseObject, NSError *_Nonnull error) {
[self handleInMainThread:task responseObject:responseObject error:error];
}];
} break;
default:
break;
}
}
- (void)startRequestComplete:(LPDBRequestComplete)complete {
[self startRequestComplete:complete progress:nil];
}
- (void)cancel {
[_task cancel];
}
- (NSDictionary *)params {
NSMutableDictionary *propertyParams = [NSMutableDictionary dictionaryWithDictionary:[self propertyDictionary]];
if ([self mapKey].allKeys.count != 0) {
for (NSString *key in [self mapKey].allKeys) {
if (propertyParams[key]) {
[propertyParams setObject:propertyParams[key] forKey:[self mapKey][key]];
[propertyParams removeObjectForKey:key];
}
}
}
return propertyParams.count == 0 ? nil : [propertyParams copy];
}
- (NSDictionary *)mapKey {
return nil;
}
- (NSString *)className {
return _className;
}
- (NSDictionary *)paramDic {
return [self params];
}
- (void)handleInMainThread:(NSURLSessionDataTask *)task responseObject:(id)responseObject error:(NSError *)error {
if ([[NSThread currentThread] isMainThread]) {
[self handleRespondse:task responseObject:responseObject error:error];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self handleRespondse:task responseObject:responseObject error:error];
});
}
}
- (void)handleRespondse:(NSURLSessionDataTask *)response responseObject:(id)responseObject error:(NSError *)error {
if (self.complete) {
if (error) {
self.complete(nil, error);
return;
}
if ([responseObject isKindOfClass:[NSDictionary class]]) {
id code = ((NSDictionary *)responseObject)[@"code"];
if ([code isKindOfClass:[NSString class]]) {
if ([code integerValue] == 0) {
NSError *parseError = nil;
if (!self.className) {
self.complete(responseObject[@"data"], nil);
return;
}
if ([responseObject[@"data"] isKindOfClass:[NSArray class]]) {
if (!self.className) {
self.complete(responseObject, nil);
return;
}
NSError *parseError = nil;
NSArray *object = [MTLJSONAdapter modelsOfClass:NSClassFromString(self.className)
fromJSONArray:responseObject[@"data"]
error:&parseError];
if (parseError) {
self.complete(nil, [NSError errorWithDomain:LPDBNetworkErrorDomain
code:LPDBNeteworkBusinessError
userInfo:@{
LPDBNetworkUserMessage: @"后台数据格式不正确"
}]);
return;
}
self.complete(object, nil);
return;
}
id object = [MTLJSONAdapter modelOfClass:NSClassFromString(self.className)
fromJSONDictionary:responseObject[@"data"]
error:&parseError];
if (parseError) {
self.complete(nil, [NSError errorWithDomain:LPDBNetworkErrorDomain
code:LPDBNeteworkBusinessError
userInfo:@{
LPDBNetworkUserMessage: @"后台数据格式不正确"
}]);;
return;
}
self.complete(object, nil);
return;
}
NSString *message = ((NSDictionary *)responseObject)[@"msg"] ? ((NSDictionary *)responseObject)[@"msg"] : @"未知错误";
if ([code integerValue] == 20000) {
[ELMKeychainUtil deleteAllInfoInKeyChain];
}
NSError *logicError = [NSError errorWithDomain:LPDBNetworkErrorDomain
code:LPDBNeteworkBusinessError
userInfo:@{
LPDBNetworkUserMessage: message,
LPDBNetworkBusinessErrorCode: code ? code : @"unknow"
}];
self.complete(nil, logicError);
return;
}
NSError *dataError = [NSError errorWithDomain:LPDBNetworkErrorDomain
code:LPDBNeteworkBusinessError
userInfo:@{
LPDBNetworkUserMessage: @"数据格式不正确"
}];
self.complete(nil, dataError);
return;
} else if ([responseObject isKindOfClass:[NSArray class]]) {
if (!self.className) {
self.complete(responseObject, nil);
return;
}
NSError *parseError = nil;
NSArray *object =
[MTLJSONAdapter modelsOfClass:NSClassFromString(self.className) fromJSONArray:responseObject[@"data"] error:&parseError];
if (parseError) {
self.complete(nil, [NSError errorWithDomain:LPDBNetworkErrorDomain
code:LPDBNeteworkBusinessError
userInfo:@{
LPDBNetworkUserMessage: @"后台数据格式不正确"
}]);
return;
}
self.complete(object, nil);
}
}
}
@end
我们先看暴露出来的属性path(路径),className(返回结果的model的类名),images(这个是上传图片支持多张上传,所以搞了个数组),paramDic(参数字典),method(请求的类型,get,post.put等),然后是2个发起请求的方法,一个带进度条一个不带。
@interface LPDBRequestObject : MTLModel
这里继承MTLModel的原因是参数可以当属性传进来;
- (NSDictionary *)paramDic {
return [self params];
}
最后说下handleRespondse方法的处理,这里主要是responseObject返回回来之后的一些基础处理,这里大家要根据自己后台的定义来写,到底是用code和status,是返回200是success还是返回0等。然后是正常返回的字典返回的是数组等的处理,因为我们最终想要的是直接拿到能用的model。还有就是被退登,登录失效等的统一处理。
好,下面我们来看下一个类LPDBHttpManager:
+ (AFHTTPSessionManager *)sharedRequestOperationManager {
static AFHTTPSessionManager *_sharedRequestOperationManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.URLCache = nil;
_sharedRequestOperationManager = [[AFHTTPSessionManager alloc]initWithBaseURL:nil];
_sharedRequestOperationManager.requestSerializer = [AFJSONRequestSerializer new];
_sharedRequestOperationManager.responseSerializer = [AFJSONResponseSerializer serializer];
_sharedRequestOperationManager.securityPolicy = [AFSecurityPolicy defaultPolicy];
_sharedRequestOperationManager.requestSerializer.timeoutInterval = 15.0; // 超时限制
_sharedRequestOperationManager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; // 忽略本地缓存
_sharedRequestOperationManager.responseSerializer.acceptableContentTypes =
[NSSet setWithObjects:@"application/json", @"text/html", @"text/json", @"text/javascript",@"text/plain" ,nil];
[_sharedRequestOperationManager.requestSerializer setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[_sharedRequestOperationManager.requestSerializer setValue:[UIApplication elm_version] forHTTPHeaderField:@"version"]; // 版本
[_sharedRequestOperationManager.requestSerializer setValue:@"AppStore" forHTTPHeaderField:@"channel"];
[_sharedRequestOperationManager.requestSerializer setValue:[UIDevice currentDevice].systemVersion forHTTPHeaderField:@"os_version"];
[_sharedRequestOperationManager.requestSerializer setValue:@"iOS" forHTTPHeaderField:@"platform_type"];
[_sharedRequestOperationManager.requestSerializer setValue:[[[UIDevice currentDevice] identifierForVendor] UUIDString] forHTTPHeaderField:@"device_id"];
[_sharedRequestOperationManager.requestSerializer setValue:[UIApplication elm_userAgent] forHTTPHeaderField:@"User-Agent"];
});
[_sharedRequestOperationManager.requestSerializer setValue:[ELMKeychainUtil valueInKeyChainForKey:YQYFINANCIALLOGINTOKEN]?:YQYFINANCIALLOGINTOKEN forHTTPHeaderField:YQYFINANCIALLOGINTOKEN];
return _sharedRequestOperationManager;
}
这里主要是一些通用header的设置,请求超时时间,缓存等。
+ (AFSecurityPolicy *)customSecurityPolicy {
// NSLog(@"security policy");
/* 配置1:验证锁定的证书,需要在项目中导入eleme.cer根证书*/
// /先导入证书
NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath
stringByAppendingPathComponent:@"/KZWFundation.bundle/KZWFundation.bundle"];
NSBundle *resource_bundle = [NSBundle bundleWithPath:bundlePath];
NSString *cerPath = [resource_bundle pathForResource:@"kongzhongjr" ofType:@"cer"]; //证书的路径
// NSLog(@"certPath % @",cerPath);
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
// NSLog(@"certData %@",certData);
// AFSSLPinningModeCertificate 使用证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
// 如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;
// validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,xn--www-u28dlq76gczcs1hq9jv9d939b73q0r2axe6d.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;
securityPolicy.pinnedCertificates = [NSSet setWithObject:certData];
/*配置1结束*/
/* 配置2:一般的验证证书,允许信任(包括系统自带的和个人安装的)的证书库中证书签名的任何证书
* 下面的配置可以验证HTTPS的证书。不过如果在iOS设备上安装了自建证书,那也会验证通过。
* 如把抓包工具的证书安装在iOS设备上,仍然可以抓到HTTPS的数据包
AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
mgr.securityPolicy = securityPolicy;
配置2结束*/
return securityPolicy;
}
这里是一个证书认证。注释已经说的比较清楚了,就不重复说了。
最后就是把头在这里拼接下。
最后一个类LPDBModel:
/**
* json key 值映射
* 属性名与json key
* @return
*/
+ (NSDictionary *)mapKey;
/**
* 过滤掉不参加映射的属性值
*
* @return
*/
+ (NSArray *)filter;
主要是2个方法一个是替换调一些不好处理的key,后台传过来的id,new啊之类的,一个是过滤掉不参加映射的属性值。
基本就这些了。数据处理的核心方法是
[MTLJSONAdapter modelsOfClass:NSClassFromString(self.className)
fromJSONArray:responseObject[@"data"]
error:&parseError];
[MTLJSONAdapter modelOfClass:NSClassFromString(self.className)
fromJSONDictionary:responseObject[@"data"]
error:&parseError];
写的有点流水账,将就看,核心就是一些通用设置和能把请求中重复的东西都抽出来,特别是数据处理这块如果都放controller的话代码量是比较多余的。然后就是请求抽出来了,比较清晰的能找到不同的request的在哪里干嘛的。
最后说下,如果看了文章的,我推荐去看下图解http,计算机网络这2本书,因为上层的东西只是怎么用,你想知道的更多做的更好,只能往底层去学习去分析。
代码地址:https://github.com/ouyrp/KZWFoundation