KZWFoundation系列之网络层封装

我们是基于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

你可能感兴趣的:(KZWFoundation系列之网络层封装)