自定义网络组件说明
1.前言
网络层在一个App中,是很重要的部分。苹果对网络请求部分已经做了很好的封装,业界在这基础上,也提供了一些相应封装,如AFNetworking、ASIHttpRequest、MKNetworkKit等。其中ASIHttpRequest、MKNetworkKit,近两年官方已经没有进行相应更新。因此在实际的App开发中,AFNetworking已经成为了事实上各大App的标准配置。
2.AFNetworking 3.0简介
2.1AFURLRequestSerialization
AFURLRequestSerialization的作用主要有两点:
- 创建NSMutableURLRequest;
- 封装好所需要的参数到NSMutableURLRequest中。
在实际开发中,我们需要做的只是选择一个满足自己需要的AFURLRequestSerialization。例如:
- (AFHTTPRequestSerializer *)requestSerializerForRequest:(HDFBaseRequest *)request { AFHTTPRequestSerializer *requestSerializer = nil; if (request.requestSerializerType == HDFRequestSerializerTypeHTTP) { requestSerializer = [AFHTTPRequestSerializer serializer]; } else if (request.requestSerializerType == HDFRequestSerializerTypeJSON) { requestSerializer = [AFJSONRequestSerializer serializer]; } requestSerializer.timeoutInterval = [request requestTimeoutInterval]; [requestSerializer setValue:@"gof_app/1.0" forHTTPHeaderField:@"User-Agent"]; //请求认证的用户名和密码 NSArray<NSString *> *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray]; if (authorizationHeaderFieldArray != nil) { //在http 头里面添加用户名和密码验证 [requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFieldArray.firstObject password:authorizationHeaderFieldArray.lastObject]; } //附加的请求Header字段 NSDictionary<NSString *, NSString *> *headerFieldValueDictionary = [request requestHeaderFieldValueDictionary]; if (headerFieldValueDictionary != nil) { for (NSString *httpHeaderField in headerFieldValueDictionary.allKeys) { NSString *value = headerFieldValueDictionary[httpHeaderField]; [requestSerializer setValue:value forHTTPHeaderField:httpHeaderField]; } } return requestSerializer; }
下面我们先看看它的类图,根据类图来进一步了解。
对于上面的类图,我们来做一个简单的讲解:
- AFURLRequestSerialization协议:定义了一个方法,需要各个子类去实现。
/** Returns a request with the specified parameters encoded into a copy of the original request. @param request The original request. @param parameters The parameters to be encoded. @param error The error that occurred while attempting to encode the request parameters. @return A serialized request. */ - (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
- AFHTTPRequestSerializer、AFJSONRequestSerializer、AFPropertyListRequestSerializer:具体的实现类,用户进行网络请求时可根据自身需求选择这三个Serializer其中之一。
序号
|
请求方式
|
结论
|
---|---|---|
1 |
get、head、delete |
调用了AFHTTPRequestSerializer的序列化方法, 把parameters拼结成URL字符串参数加到URL后面作为请求的一部分 |
2 |
multipart表单提交 |
在这里面就会用到AFStreamingMultipartFormData,此时parameters及通过AFStreamingMultipartFormData添 |
3 | put、post、patch |
AFHTTPRequestSerializer:把parameters拼接成字符串参数后序列化成NSData放到HTTPRequest的HTTPBody中。 |
- AFMultipartFormData协议:定义了一些接口方法,允许用户可以用不同的方式添加表单的内容,如:使用文件路径、直接用NSData、或使用inputStream等。
- AFStreamingMultipartFormData:遵循了AFMultipartFormData协议,把协议的方法都实现了。
- AFMultipartBodyStream:它起着一个重要桥梁作用,上传表单数据时系统会先调到它,然后它会依赖AFHTTPBodyPart读到数据,然后把数据返回给URL系统。
- AFHTTPBodyPart:每一个AFHTTPBodyPart就是代表一项表单数据,由它真正读取它内部的数据。
2.2AFURLResponseSerialization
AFURLResponseSerialization的主要作用是为我们请求所得到的数据提供验证并进行反序列化,我们只需要按自己的需求选择合适的AFURLResponseSerialization即可,反序列成功后就能得我们想要的结果。
下面是AFURLResponseSerialization类图:
AFURLResponseSerialization协议:定义了一个方法,需要各个子类去实现:
/** The response object decoded from the data associated with a specified response. @param response The response to be processed. @param data The response data to be decoded. @param error The error that occurred while attempting to decode the response data. @return The object decoded from the specified response data. */ - (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
-
AFHTTPResponseSerializer:其它各个子类的基类。
-
AFJSONResponseSerializer:JSON;
- AFXMLParserResponseSerializer:XML,只能返回XMLParser
-
AFPropertyListResponseSerializer:PList
-
AFImageResponseSerializer:Image
-
AFCompoundResponseSerializer:组合
2.3AFSecurityPolicy
AFSecurityPolicy是AFNetworking安全相关的模块,上次HTTPS项目就使用了它。对数字证书感兴趣的,可以看看这篇文章:http://www.360doc.com/content/13/0809/14/1073512_305848184.shtml
在AFSecurityPolicy中,提供了3种验证模式:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
- AFSSLPinningModeNone:这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。
- AFSSLPinningModePublicKey:这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
- AFSSLPinningModeCertificate:这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步:第一步验证证书的域名/有效期等信息;第二步是对比服务端返回的证书跟客户端返回的是否一致。
以上三种模式的逻辑都在 - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain;方法中实现了,感兴趣的可以看看。对于如何获取证书,获取公钥,这些都有系统接口实现。
【备注】:一般来说,每个版本的iOS设备中,都会包含一些既有的CA根证书。如果接收到的证书是iOS信任的CA根证书签名的,那么则为合法证书;否则则为“非法”证书。
2.4AFHTTPSessionManager
通过前面三小块的讲解,大家可能对AFNetworking的工作模式还是一头雾水,不知道是怎么一回事,先别慌,我们慢慢唠。下面我们来看一下AFNetworking的工作流程概图:
从上图可以看出,整个网络请求的核心是AFHTTPSessionManager。和前面一样,我们先来看一下相关类图:
从上面的类图可以看到,AFHTTPSessionManager集成自AFURLSessionManager,那么这两个类都是干什么用的呢?
2.4.1AFURLSessionManager
AFURLSessionManager 创建并管理着NSURLSession这个对象,NSURLSession基于NSURLSessionConfiguration。我们来分析一下AFURLSessionManager的头文件:
- AFURLSessionManager实现了六个协议,其中包含:NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate。
-
属性session:关于NSURLSession,可以查看官方文档。相对于NSURLConnection ,NSURLSession强大的功能是支持后台上传和下载。不过值得注意的是,这个对象与它的delegate之间的是一个强引用关系,因此在释放NSURLSession时,要做好处理。
-
属性operationQueue:为NSURLSession 绑定一个队列,用于请求过程中的一系列事件在哪个 OperationQueue 回调,这里是设置了最大并发量为1的队列。
-
属性responseSerializer:序列化响应数据的对象,默认的模式是AFJSONResponseSerializer,而且不能为空。
-
属性securityPolicy:安全策略,默认是defaultPolicy。
- 属性reachabilityManager:网络监控管理者。
- 当前任务管理集合的相应属性:tasks、dataTasks、uploadTasks、downloadTasks。
- 属性completionQueue:请求成功后,回调block会在这个队列中调用,如果为空,就在主队列。
- 属性completionGroup:请求成功后,回调block会在这个组中调用,如果为空,就使用一个私有的。
-
属性attemptsToRecreateUploadTasksForBackgroundSessions:用来解决在后台创建上传任务返回nil的bug,默认为NO,如果设为YES,在后台创建上传任务失败会尝试重新创建该任务。
-
其他:相应的方法,应该看一下名称能大概猜到功能。
每当有一个新的任务,都会创建一个对应的AFURLSessionManagerTaskDelegate,并且以task的@(taskIdentifier)作为key,AFURLSessionManagerTaskDelegate作为值保存在字典里。AFURLSessionManagerTaskDelegate代理对象用于处理上传或下载的进度以及处理获取完数据后的行为。从它的一系列属性可以看到:
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate> @property (nonatomic, weak) AFURLSessionManager *manager; @property (nonatomic, strong) NSMutableData *mutableData; //!<用于接收数据 @property (nonatomic, strong) NSProgress *uploadProgress; //!<上传进度 @property (nonatomic, strong) NSProgress *downloadProgress; //!<下载进度 @property (nonatomic, copy) NSURL *downloadFileURL; //!<下载路径 @property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading; //!<用于返回下载完成后的文件路径 @property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock; //!<上传时调用 @property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock; //!<下载时调用 @property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler; //!<完成时调用 @end
2.4.2AFHTTPSessionManager
AFHTTPSessionManager是AFURLSessionManager的子类,是专门为HTTP请求设计的。它提供了一系列方法来帮助我们创建具体的请求,是我们在实际开发中经常使用的。
3.GofNetComponent的实现
通过上面对AFNetworking的介绍,我们接下来一步步讲解GofNetComponent组件。首先,提出三个问题:
问题一:使用什么方式和业务层做数据对接?
问题二:交付什么样的数据给业务层?
问题三:采用集约型还是离散型的调用方式?
这几个问题,是在做GofNetComponent组件过程中,一直在思考的问题,组件开发的过程中,也是就这几个问题,调整了部分策略。现在针对这几个问题,谈谈个人的想法。
问题一:使用什么方式和业务层做数据对接?
网络层和业务层的数据对接,业界现在使用的方式大致有这几种:Delegate,Notification,Block,KVO,Target-Action。使用频率高的主要有两种:Delegate,Block。阿里的网络层Delegate、Block以及target-action都使用了,边锋主要是采用的block,大智慧主要采用的是Notification,安居客早期以Block为主,后面改成了以Delegate为主,我们目前是使用的block。这个没有对错与否的区别,只是使用习惯和架构师的风格。Block很灵活,代码都写在一起,显得紧凑。但是在一段代码里,包含我们请求时的参数组装、参数的判断、请求发起、返回数据校验、各种判断的弹出提示、对返回数据进行处理,这些会让请求这段代码变得非常臃肿。当然,业务层也可以为Block开一个单独的方法来处理返回结果,但是这样可能导致各个业务处理的方法名不一致,可读性差,因此还不如在网络层架构的时候,就直接使用代理的方式给到业务开发,这样就把请求和返回分离了,代码结构更清晰,方法命名也更统一。
综上所述,在实际业务层的开发过程中,建议以Delegate为主,Block为辅。
问题二:交付什么样的数据给业务层?
这个在最开始的时候,是在GofNetComponent组件中集成YYModel,直接在组件中进行处理,并返回Model数据。后面思考了一下,感觉这样不好。一方面是导致GofNetComponent依赖YYModel,独立性更差;另一方面是如果直接返回model的话,对业务层限制太死了,因此个人感觉网络组件这个时候应该更灵活。
问题三:采用集约型还是离散型的调用方式?
首先解释一下这两个概念:
- 集约型API:所有API的调用只有一个类,然后这个类接收API名字、API参数、以及回调着陆点(可以是target-action、block、delegate等)作为参数。然后执行类似startRequest这样的方法,它就会去根据这些参数起飞去调用API了,然后获得API数据之后再根据指定的着陆点去着陆。例如我们的HDFNet。
- 离散型API:一个API对应于一个APIManager类,然后API名字、API参数、着陆方式等都已经集成入APIManager中。
理解概念之后,我们从几个角度来分析一下这两种类型:
复杂度:集约型API对于编码的要求要低一些,一方面是因为没有了基类和各种继承关系,所需要的源代码文件就要少很多;另一方面离散型API每一个请求类的 URL、参数等等都需要单独处理;
灵活性:离散型API在灵活性上是要强出很多的,通过不同的配置,可以对接口实现更多个性化的控制。
模块化:集约型API实现了层次之间的解耦,但是没有实现横向的模块化。假设有不同的人在开发不同的业务,集约型的设计很可能会导致大家都要去修改同一个文件。而离散型的设计则完全避免了这一点,对于不同的业务来说,修改的文件不会是同一个,互相之间不会产生依赖,在此基础上可以很方便地对于代码进行模块化的分割。
通过上面三个问题的分析,大家对网络层的设计思想应该有个大致的了解,下面我们来看看GofNetComponent的类图:
对于上面的类图,我们简单介绍一下各个类的职责:
- GofNetworkAgent:负责管理所有的网络请求;
- GofBaseRequest:网络请求基类,提供网络请求类的基本操作与接口;
- GofRequest:继承了GofBaseRequest 的所有功能,此外增加了对缓存的处理,是所有增加缓存功能请求的基类,我们项目中需要缓存的请求类都需要直接或间接继承与它;
- GofBatchRequest:批量网络请求类,通过此类来实现批量发送请求;
- GofNetConfig:负责统一为网络请求加上一些参数,或者修改一些路径;
- GofNetMonitoring:网络状态监测类;
- GofNetworkUtils:工具类,提供一些参数加密和JSON数据检查的接口;
- GofUrlArgumentsFilter:添加公用参数;
- GofNetResponse:网络返回数据初步解析;
4.GofNetComponent的功能
通过上面部分的介绍,那么GofNetComponent都提供了哪些功能呢?
- 支持网络状态监测;
- 支持网络的自动检查,无网则不发送任何请求;
- 支持自动检测请求发送状态,避免同一请求多次提交;
- 支持按时间、版本号、设置版本缓存网络请求内容(包括下载文件);
- 支持统一设置/变更服务器地址;
- 支持检查返回 JSON 内容的合法性;
- 支持文件的断点续传;
- 支持 block 和 delegate 两种模式的回调方式;
- 支持批量的网络请求发送,并统一设置它们的回调
- 支持网络请求公用参数的统一添加(可多次添加);