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 <Foundation/Foundation.h>
#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 <Foundation/Foundation.h>
#import <Mantle/MTLModel.h>
#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 <Mantle/MTLJSONAdapter.h>
#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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,639评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,093评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,079评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,329评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,343评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,047评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,645评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,565评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,095评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,201评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,338评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,014评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,701评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,194评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,320评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,685评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,345评论 2 358

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,401评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,107评论 1 32
  • OC语言基础 1.类与对象 类方法 OC的类方法只有2种:静态方法和实例方法两种 在OC中,只要方法声明在@int...
    奇异果好补阅读 4,281评论 0 11
  • 又到年底了,今年的年终总结比去年早,因为从11月开始的变化已经在预料之中。今年经历了很多大起大落的变化,很多人生的...
    木易夕口阅读 256评论 0 1
  • 01 吴吴山,清代著名的文人学士,有名的剧评家,与洪昇为亲友,曾评点过《长生殿》。 他前后娶的三位妻子,陈同、谈则...
    十字死光阅读 579评论 0 3