YTKNetwork
是猿题库技术团队开源的一个网络请求框架,内部封装了AFNetWorking,他把每个请求实例化,管理他的生命周期,也可以管理多个请求。属于离散型网络请求框架。
- 集约型:
AFNetworking
,业务层提供一个方法,所有业务层的网络请求都要通过该方法完成。即项目中的每个请求都会走统一的入口,对外暴露了请求的URL和 Param以及请求方式,入口一般都是通过单例来实现。有较好的灵活性,可以方便的在任何位置发起请求,同时也可能是不好的地方,网络请求回调散落在各处,不便于维护。 - 离散型:
YTKNetwork
,离散型最大的特点是一个网络请求对应一个单独的类,在这个类内部封装请求地址,方式,参数,校验和处理请求回来的数据,通常代理回调,需要跨层传递数据时也使用通知回调,比较集中,因为数据处理都放在内部处理了,返回数据的形式(模型化后的数据之类的)不需要控制器关心,只需要在请求完成操作数据,让VC更轻量化。
1. 接入方式
- pod 'YTKNetwork'
- 直接github.com/yuantiku/YT…
2. 设计模式
YTKNetwork
框架采用的设计模式是命令模式(Command Pattern)。 首先看一下命令模式的定义:
命令模式将请求封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
- 详细介绍一下:
- 命令模式的本质是对命令的封装,将发出的责任和执行命令的责任分割开。
- 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式的类图结合餐厅点餐的流程例子,我们来理清一下YTKNetwork内部的职能。
场景 | Client | Command | ConcreteCommand | Invoker | Receiver |
---|---|---|---|---|---|
客人 | 餐厅 | 空白订单 | 填入菜名的订单 | 服务员 | 厨师 |
YTKNetwork | ViewController/ViewModel | YTKBaseRequest | CustomRequest | YTKNetworkAgent | AFNetworking |
可以看到,YTKNetwork
对命令模式的实现是很符合其设计标准的,它将请求的发起者和接收者分离开来(中间隔着调用者),可以让我们随时更换接受者。
3. 网络架构及责任介绍
YTKNetwork框架将每一个请求实例化
YTKBaseRequest
:是所有请求类的基类,YTKRequest
是它的子类。所以如果我们想要发送一个请求,则需要创建并实例化一个继承于YTKRequest
的自定义的请求类(CustomRequest
)并发送请求。持有NSURLSessionTask
实例,定义了Request
的相关属性,Block
和Delegate
。给对外接口默认的实现,以及公共逻辑。命令YTKNetworkAgent
发起网络请求。YTKRequest
:YTKBaseRequest
的子类。负责缓存的处理:请求前查询缓存;请求后写入缓存。这里采用归档形式缓存,请求方式、根路径、请求地址、请求参数、app版本号、敏感数据拼接再MD5作为缓存的文件名,保证唯一性。还提供设置缓存的保存时长,主要实现是通过获取缓存文件上次修改的时刻距离现在的时间和设置的缓存时长作比较,来判断是否真正发起请求。YTKNetworkConfig
:被YTKRequest
和YTKNetworkAgent
访问。负责所有请求的全局配置,例如baseUrl和CDNUrl等等。YTKNetworkAgent
:是一个单例,负责管理所有的请求类(例如CustomRequest
)。当CustomRequest
发送请求以后,会把自己放在YTKNetworkAgent
持有的一个字典里,让其管理自己。(我们说YTKNetwork
封装了AFNetworking
,实际上是YTKNetworkAgent
封装了AFNetworking
,由它负责AFNetworking
请求的发送和AFNetworking
的回调处理。所以如果我们想更换一个第三方网络请求库,就可以在这里更换一下。而YTKRequest
更多的是只是负责缓存的处理。)YTKNetworkPrivate
:提供JSON验证,appVersion等辅助性的方法;给YTKBaseRequest
增加一些分类。YTKBatchRequest
:可以发起批量请求,持有一个数组来保存所有的请求类。在请求执行后遍历这个数组来发起请求,如果其中有一个请求返回失败,则认定本组请求失败。YTKBatchRequestAgent
:负责管理多个YTKBatchRequest
实例,持有一个数组来保存YTKBatchRequest
。支持添加和删除YTKBatchRequest
实例。YTKChainRequest
:可以发起链式请求,持有一个数组来保存所有的请求类。当某个请求结束后才能发起下一个请求,如果其中有一个请求返回失败,则认定本请求链失败。YTKChainRequestAgent
:负责管理多个YTKChainRequestAgent
实例,持有一个数组来保存YTKChainRequest
。支持添加和删除YTKChainRequest
实例。
4. 应用及源码解析
4.1 单个请求
4.1.1 单个请求的配置
官方的教程建议我们将请求的全局配置是在AppDelegate.m
文件里,设定baseUrl
以及cdnUrl
等参数。
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
YTKNetworkConfig *config = [YTKNetworkConfig sharedConfig];
config.baseUrl = @"http://yuantiku.com";
config.cdnUrl = @"http://fen.bi";
}
复制代码
如果我们需要新建一个注册的请求,则需要创建一个继承于YTKRequest
的注册接口的类RegisterApi
,并将针对该请求参数配置好:
// RegisterApi.h
#import "YTKRequest.h"
@interface RegisterApi : YTKRequest
- (id)initWithUsername:(NSString *)username password:(NSString *)password;
@end
// RegisterApi.m
#import "RegisterApi.h"
@implementation RegisterApi {
NSString *_username;
NSString *_password;
}
- (id)initWithUsername:(NSString *)username password:(NSString *)password {
self = [super init];
if (self) {
_username = username;
_password = password;
}
return self;
}
- (NSString *)requestUrl {
// “ http://www.yuantiku.com ” 在 YTKNetworkConfig 中设置,这里只填除去域名剩余的网址信息
return @"/iphone/register";
}
- (YTKRequestMethod)requestMethod {
return YTKRequestMethodPOST;
}
- (id)requestArgument {
return @{
@"username": _username,
@"password": _password
};
}
@end
复制代码
4.1.2 单个请求的发起
在实例化完成RegisterApi
之后,具体如何使用呢?直接调用startWithCompletionBlockWithSuccess:failure方法(或start方法)
就可以发起它:
- (void)loginButtonPressed:(id)sender {
NSString *username = self.UserNameTextField.text;
NSString *password = self.PasswordTextField.text;
if (username.length > 0 && password.length > 0) {
RegisterApi *api = [[RegisterApi alloc] initWithUsername:username password:password];
[api startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
// 你可以直接在这里使用 self
NSLog(@"succeed");
} failure:^(YTKBaseRequest *request) {
// 你可以直接在这里使用 self
NSLog(@"failed");
}];
}
}
复制代码
上面是以block的形式回调,YTKNetwork也支持代理的回调:
- (void)loginButtonPressed:(id)sender {
NSString *username = self.UserNameTextField.text;
NSString *password = self.PasswordTextField.text;
if (username.length > 0 && password.length > 0) {
RegisterApi *api = [[RegisterApi alloc] initWithUsername:username password:password];
api.delegate = self;
[api start];
}
}
- (void)requestFinished:(YTKBaseRequest *)request {
NSLog(@"succeed");
}
- (void)requestFailed:(YTKBaseRequest *)request {
NSLog(@"failed");
}
复制代码
必须给自定义请求类(
RegisterApi
)调用startWithCompletionBlockWithSuccess:failure方法(或start方法)
,才能真正发起请求。同时设置了回调代理和回调block的情况下,首先回调的是回调代理方法,然后再走回调block。
4.1.3 单个请求源码分析
知道了YTKRequest
请求是如何在外部发起的,我们现在从startWithCompletionBlockWithSuccess:failure
方法开始,来看一下YTKNetwork都做了什么:
YTKBaseRequest
类(因为最早是由它定义的该方法):
//YTKBaseRequest.m
//传入成功和失败的block,并保存起来
- (void)startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
//保存成功和失败的回调block,便于将来调用
[self setCompletionBlockWithSuccess:success failure:failure];
//发起请求
[self start];
}
//保存成功和失败的回调block,便于将来调用
- (void)setCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
self.successCompletionBlock = success;
self.failureCompletionBlock = failure;
}
复制代码
YTKRequest
类(注意,虽然YTKBaseRequest
也实现了start
方法,但是由于YTKRequest
类是它的子类并也实现了start
方法,所以这里最先走的是YTKRequest类的start
方法):
//YTKRequest.m
- (void)start {
//1 如果忽略缓缓存->请求
if (self.ignoreCache) {
[self startWithoutCache];
return;
}
// Do not cache download request.
//2 如果存在下载未完成的->请求
if (self.resumableDownloadPath) {
[self startWithoutCache];
return;
}
//3 获取缓存失败->请求
if (![self loadCacheWithError:nil]) {
[self startWithoutCache];
return;
}
// 4 到这里说明一定能拿到缓存,可以直接回调了
_dataFromCache = YES;
dispatch_async(dispatch_get_main_queue(), ^{
//5. 回调之前的操作
//5.1 缓存处理
[self requestCompletePreprocessor];
//5.2 用户可以在这里进行真正回调前的操作
[self requestCompleteFilter];
YTKRequest *strongSelf = self;
//6. 执行回调
//6.1 请求完成的代理
[strongSelf.delegate requestFinished:strongSelf];
// 6.2 请求成功的 block
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
// 7. 把成功和失败的block都设置为nil,避免循环引用
[strongSelf clearCompletionBlock];
[guofendexiaoxuetaiguofenzhuniwanshangmeiyourourouchinihaoyisima]
});
}
复制代码
我们之前说过YTKRequest负责缓存的相关处理,所以在上面这个start方法里,它做的是请求之前缓存的查询和检查工作,如果忽略缓存,或者缓存获取失败,调用[self startWithoutCache]
(1~3)发起请求,如果成功获取缓存,则直接回调。
(1)ignoreCache
属性是用户手动设置的,如果用户强制忽略缓存,则无论缓存是否存在,直接发起请求。
(2)resumableDownloadPath
是断点下载路径,如果该路径不为空,说明有未完成的下载任务,则直接发送请求继续下载。
(3)loadCacheWithError
方法验证了加载缓存是否成功的方法(返回YES,说明可以加载缓存),具体实现:
//YTKRequest.m
- (BOOL)loadCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// Make sure cache time in valid.
//缓存时间小于0,则返回(默认缓存时间为-1,需要用户手动设置,单位是秒)
if ([self cacheTimeInSeconds] < 0) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheTime userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache time"}];
}
return NO;
}
// Try load metadata.
//是否有缓存的元数据,如果没有,返回错误
if (![self loadCacheMetadata]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidMetadata userInfo:@{ NSLocalizedDescriptionKey:@"Invalid metadata. Cache may not exist"}];
}
return NO;
}
// Check if cache is still valid.
//有缓存再验证是否有效
if (![self validateCacheWithError:error]) {
return NO;
}
// Try load cache.
//有缓存,而且有效,再验证是否能取出来
if (![self loadCacheData]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheData userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache data"}];
}
return NO;
}
return YES;
}
复制代码
先讲一下什么是元数据:元数据是指数据的数据,在这里描述了缓存数据本身的一些特征:包括版本号,缓存时间,敏感信息等等, 稍后会做详细介绍。
我们来看一下上面关于缓存的元数据的获取方法:loadCacheMetadata
方法。
//YTKRequest.m
- (BOOL)loadCacheMetadata {
NSString *path = [self cacheMetadataFilePath];
NSFileManager * fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
@try {
//将序列化之后被保存在磁盘里的文件反序列化到当前对象的属性cacheMetadata
_cacheMetadata = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return YES;
} @catch (NSException *exception) {
YTKLog(@"Load cache metadata failed, reason = %@", exception.reason);
return NO;
}
}
return NO;
}
复制代码
cacheMetadata(YTKCacheMetadata)
是当前reqeust类用来保存缓存元数据的属性。YTKCacheMetadata
类被定义在YTKRequest.m
文件里面,并支持序列化处理,可以保存在磁盘里。
//YTKRequest.m
@interface YTKCacheMetadata : NSObject<NSSecureCoding>
//缓存版本号
@property (nonatomic, assign) long long version;
//敏感信息
@property (nonatomic, strong) NSString *sensitiveDataString;
@property (nonatomic, assign) NSStringEncoding stringEncoding;
//创建时间
@property (nonatomic, strong) NSDate *creationDate;
//app版本
@property (nonatomic, strong) NSString *appVersionString;
@end
复制代码
现在获取了缓存的元数据并赋给了自身的cacheMetadata
属性上,那么接下来就要逐一验证元数据里的各项信息是否符合要求,在validateCacheWithError:
里面验证:
//YTKRequest.m
- (BOOL)validateCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// Date
//是否大于过期时间
NSDate *creationDate = self.cacheMetadata.creationDate;
NSTimeInterval duration = -[creationDate timeIntervalSinceNow];
if (duration < 0 || duration > [self cacheTimeInSeconds]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorExpired userInfo:@{ NSLocalizedDescriptionKey:@"Cache expired"}];
}
return NO;
}
// Version 缓存的版本号是否符合
long long cacheVersionFileContent = self.cacheMetadata.version;
if (cacheVersionFileContent != [self cacheVersion]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorVersionMismatch userInfo:@{ NSLocalizedDescriptionKey:@"Cache version mismatch"}];
}
return NO;
}
// Sensitive data敏感信息是否符合
NSString *sensitiveDataString = self.cacheMetadata.sensitiveDataString;
NSString *currentSensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
if (sensitiveDataString || currentSensitiveDataString) {
// If one of the strings is nil, short-circuit evaluation will trigger
if (sensitiveDataString.length != currentSensitiveDataString.length || ![sensitiveDataString isEqualToString:currentSensitiveDataString]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorSensitiveDataMismatch userInfo:@{ NSLocalizedDescriptionKey:@"Cache sensitive data mismatch"}];
}
return NO;
}
}
// App version app 版本是否符合
NSString *appVersionString = self.cacheMetadata.appVersionString;
NSString *currentAppVersionString = [YTKNetworkUtils appVersionString];
if (appVersionString || currentAppVersionString) {
if (appVersionString.length != currentAppVersionString.length || ![appVersionString isEqualToString:currentAppVersionString]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorAppVersionMismatch userInfo:@{ NSLocalizedDescriptionKey:@"App version mismatch"}];
}
return NO;
}
}
return YES;
}
复制代码
如果每项元数据信息都能通过,再在loadCacheData
方法里面验证缓存是否能被取出来:
//YTKRequest.m
- (BOOL)loadCacheData {
NSString *path = [self cacheFilePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
NSData *data = [NSData dataWithContentsOfFile:path];
_cacheData = data;
_cacheString = [[NSString alloc] initWithData:_cacheData encoding:self.cacheMetadata.stringEncoding];
switch (self.responseSerializerType) {
case YTKResponseSerializerTypeHTTP:
// Do nothing.
return YES;
case YTKResponseSerializerTypeJSON:
_cacheJSON = [NSJSONSerialization JSONObjectWithData:_cacheData options:(NSJSONReadingOptions)0 error:&error];
return error == nil;
case YTKResponseSerializerTypeXMLParser:
_cacheXML = [[NSXMLParser alloc] initWithData:_cacheData];
return YES;
}
}
return NO;
}
复制代码
当确认缓存可以成功取出后,手动设置dataFromCache
属性为YES,说明当前的请求结果是来自于缓存,而没有通过网络请求。
然后在真正回调之前做了如下处理:
//YTKRequest.m:
- (void)start{
....
//5. 回调之前的操作
//5.1 缓存处理
[self requestCompletePreprocessor];
//5.2 用户可以在这里进行真正回调前的操作
[self requestCompleteFilter];
....
}
复制代码
-
- 5.1:
requestCompletePreprocessor
方法:
- 5.1:
//YTKRequest.m:
- (void)requestCompletePreprocessor {
[super requestCompletePreprocessor];
//是否异步将responseData写入缓存(写入缓存的任务放在专门的队列ytkrequest_cache_writing_queue进行)
if (self.writeCacheAsynchronously) {
dispatch_async(ytkrequest_cache_writing_queue(), ^{
//保存响应数据到缓存
[self saveResponseDataToCacheFile:[super responseData]];
});
} else {
//保存响应数据到缓存
[self saveResponseDataToCacheFile:[super responseData]];
}
}
复制代码
//YTKRequest.m:保存响应数据到缓存
- (void)saveResponseDataToCacheFile:(NSData *)data {
if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {
if (data != nil) {
@try {
// New data will always overwrite old data.
[data writeToFile:[self cacheFilePath] atomically:YES];
YTKCacheMetadata *metadata = [[YTKCacheMetadata alloc] init];
metadata.version = [self cacheVersion];
metadata.sensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
metadata.stringEncoding = [YTKNetworkUtils stringEncodingWithRequest:self];
metadata.creationDate = [NSDate date];
metadata.appVersionString = [YTKNetworkUtils appVersionString];
[NSKeyedArchiver archiveRootObject:metadata toFile:[self cacheMetadataFilePath]];
} @catch (NSException *exception) {
YTKLog(@"Save cache failed, reason = %@", exception.reason);
}
}
}
}
复制代码
我们可以看到
requestCompletePreprocessor
方法的任务是将响应数据保存起来,也就是做缓存。但是,缓存的保存有两个条件,一个是需要cacheTimeInSeconds
方法返回正整数(缓存时间,单位是秒,后续会详细说明);另一个条件是isDataFromCache
方法返回NO
。 但是我们知道,如果缓存可用,就会将这个属性设置为YES
,所以走到这里的时候,就不做缓存了。
-
- 接着看下5.2:
requestCompleteFilter
方法则是需要用户自己提供具体实现的,专门作为回调成功之前的一些处理:
- 接着看下5.2:
//YTKBaseRequest.m
- (void)requestCompleteFilter {
}
复制代码
到这里,回调之前的处理都结束了,下面来看一下在缓存可用的情况下的回调:
//YTKRequest.m
- (void)start{
...
YTKRequest *strongSelf = self;
//6. 执行回调
//6.1 请求完成的代理
[strongSelf.delegate requestFinished:strongSelf];
//6.2 请求成功的block
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
//7. 把成功和失败的block都设置为nil,避免循环引用
[strongSelf clearCompletionBlock];
}
复制代码
我们可以看到 ,这里面同时存在两种回调:代理的回调和block的回调。先执行的是代理的回调,然后执行的是block的回调。而且在回调结束之后,YTKNetwork会帮助我们清空回调的block:
//YTKRequest.m
- (void)clearCompletionBlock {
// nil out to break the retain cycle.
//清除请求结束的block,避免循环引用
self.successCompletionBlock = nil;
self.failureCompletionBlock = nil;
}
复制代码
到这里,我们了解了YTKNetwork
在网络请求之前是如何验证缓存,以及在缓存有效的情况下是如何回调的。
反过来,如果缓存无效(或忽略缓存)时,需要立即请求网络。那么我们现在来看一看在这个时候YTKNetwork
都做了什么:
仔细看一下上面的start
方法,我们会发现,如果缓存不满足条件时,会直接调用startWithoutCache
方法:
//YTKRequest.m
- (void)start{
//1. 如果忽略缓存 -> 请求
if (self.ignoreCache) {
[self startWithoutCache];
return;
}
//2. 如果存在下载未完成的文件 -> 请求
if (self.resumableDownloadPath) {
[self startWithoutCache];
return;
}
//3. 获取缓存失败 -> 请求
if (![self loadCacheWithError:nil]) {
[self startWithoutCache];
return;
}
......
}
复制代码
那么在startWithoutCache
方法里都做了什么呢?
//YTKRequest.m
- (void)startWithoutCache {
//1.清除缓存
[self clearCacheVariables];
//2.调用父类的发起请求
[super start];
}
//清除当前请求对应的所有缓存
- (void)clearCacheVariables {
_cacheData = nil;
_cacheXML = nil;
_cacheJSON = nil;
_cacheString = nil;
_cacheMetadata = nil;
_dataFromCache = NO;
}
复制代码
YTKBaseRequest
在这里,首先清除了关于缓存的所有数据,然后调用父类的start
方法:
//YTKBaseRequest.m
- (void)start {
//1.告诉Accessories即将回调了(其实是即将发起请求)
[self toggleAccessoriesWillStartCallBack];
//2.令agent添加请求并发起请求,在这里并不是组合关系,agent只是个单例
[[YTKNetworkAgent sharedAgent] addRequest:self];
}
复制代码
第一步里的Accessories
是一些遵从<YTKRequestAccessory>
代理的对象。这个代理定义了一些用来追踪请求状况的方法。它被定义在了YTKBaseRequest.h
文件里:
//用来跟踪请求状态的代理
@protocol YTKRequestAccessory <NSObject>
@optional
/// Inform the accessory that the request is about to start.
///
/// @param request The corresponding request.
- (void)requestWillStart:(id)request;
/// Inform the accessory that the request is about to stop. This method is called
/// before executing `requestFinished` and `successCompletionBlock`.
///
/// @param request The corresponding request.
- (void)requestWillStop:(id)request;
/// Inform the accessory that the request has already stoped. This method is called
/// after executing `requestFinished` and `successCompletionBlock`.
///
/// @param request The corresponding request.
- (void)requestDidStop:(id)request;
@end
复制代码
所以只要某个对象遵从了这个代理,就可以追踪到请求将要开始,将要结束,已经结束的状态。
YTKNetworkAgent
接着看一下第二步:YTKNetworkAgent
把当前的请求对象添加到了自己身上并发送请求。来看一下它的具体实现:
//YTKNetworkAgent
- (void)addRequest:(YTKBaseRequest *)request {
//1. 获取task
NSParameterAssert(request != nil);
NSError * __autoreleasing requestSerializationError = nil;
//获取用户buildCustomUrlRequest自定义的requestURL
NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
if (customUrlRequest) {
__block NSURLSessionDataTask *dataTask = nil;
//如果存在用户buildCustomUrlRequest自定义的request,则直接走AFNetworking的dataTaskWithRequest:方法
dataTask = [_manager dataTaskWithRequest:customUrlRequest completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
//响应的统一处理
[self handleRequestResult:dataTask responseObject:responseObject error:error];
}];
request.requestTask = dataTask;
} else {
//如果用户没有buildCustomUrlRequest自定义Url,则直接走这里
request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
}
//序列化失败,则认为请求失败
if (requestSerializationError) {
//请求失败的处理
[self requestDidFailWithRequest:request error:requestSerializationError];
return;
}
NSAssert(request.requestTask != nil, @"requestTask should not be nil");
// 优先级的映射
// !!Available on iOS 8 +
if ([request.requestTask respondsToSelector:@selector(priority)]) {
switch (request.requestPriority) {
case YTKRequestPriorityHigh:
request.requestTask.priority = NSURLSessionTaskPriorityHigh;
break;
case YTKRequestPriorityLow:
request.requestTask.priority = NSURLSessionTaskPriorityLow;
break;
case YTKRequestPriorityDefault:
/*!!fall through*/
default:
request.requestTask.priority = NSURLSessionTaskPriorityDefault;
break;
}
}
// Retain request
YTKLog(@"Add request: %@", NSStringFromClass([request class]));
//2. 将request放入保存请求的字典中,taskIdentifier为key,request为值
[self addRequestToRecord:request];
//3. 开始task
[request.requestTask resume];
}
复制代码
这个方法挺长的,但是请不要被吓到,它总共分为三个部分:
第一部分是获取当前请求对应的task并赋给request的requestTask属性(以后提到的request,都为用户自定义的当前请求类的实例)。 第二部分是把request放入专门用来保存请求的字典中,key为taskIdentifier。 第三部分是启动task。 下面我来依次讲解每个部分:
(1) 获取当前请求对应的task并赋给request:
//YTKNetworkAgent.m
- (void)addRequest:(YTKBaseRequest *)request {
...
if (customUrlRequest) {
__block NSURLSessionDataTask *dataTask = nil;
//如果存在用户自定义request,则直接走AFNetworking的dataTaskWithRequest:方法
dataTask = [_manager dataTaskWithRequest:customUrlRequest completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
//统一处理请求响应
[self handleRequestResult:dataTask responseObject:responseObject error:error];
}];
request.requestTask = dataTask;
} else {
//如果用户没有自定义url,则直接走这里
request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
}
...
}
复制代码
在这里判断了用户是否buildCustomUrlRequest自定义了request:
如果是,会忽略其他的一切自定义request
的方法,例如requestUrl
,requestArgument
,requestMethod
,requestSerializerType
,requestHeaderFieldValueDictionary
等等。则直接调用AFNetworking的dataTaskWithRequest:方法。
如果不是,则调用YTKRequest自己的生成task的方法。
第一种情况就不说了,因为AF帮我们做好了。在这里看一下第二种情况,sessionTaskForRequest: error :
方法内部:
//YTKNetworkAgent.m
//根据不同请求类型,序列化类型,和请求参数来返回NSURLSessionTask
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
// 1. 获得请求类型(GET,POST等)
YTKRequestMethod method = [request requestMethod];
2. 获得请求url
NSString *url = [self buildRequestUrl:request];
//3. 获得请求参数
id param = request.requestArgument;
AFConstructingBlock constructingBlock = [request constructingBodyBlock];
//4. 获得request serializer
AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];
//5. 根据不同的请求类型来返回对应的task
switch (method) {
case YTKRequestMethodGET:
if (request.resumableDownloadPath) {
//下载任务
return [self downloadTaskWithDownloadPath:request.resumableDownloadPath requestSerializer:requestSerializer URLString:url parameters:param progress:request.resumableDownloadProgressBlock error:error];
} else {
//普通get请求
return [self dataTaskWithHTTPMethod:@"GET" requestSerializer:requestSerializer URLString:url parameters:param error:error];
}
case YTKRequestMethodPOST:
//POST请求
return [self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameters:param constructingBodyWithBlock:constructingBlock error:error];
case YTKRequestMethodHEAD:
//HEAD请求
return [self dataTaskWithHTTPMethod:@"HEAD" requestSerializer:requestSerializer URLString:url parameters:param error:error];
case YTKRequestMethodPUT:
//PUT请求
return [self dataTaskWithHTTPMethod:@"PUT" requestSerializer:requestSerializer URLString:url parameters:param error:error];
case YTKRequestMethodDELETE:
//DELETE请求
return [self dataTaskWithHTTPMethod:@"DELETE" requestSerializer:requestSerializer URLString:url parameters:param error:error];
case YTKRequestMethodPATCH:
//PATCH请求
return [self dataTaskWithHTTPMethod:@"PATCH" requestSerializer:requestSerializer URLString:url parameters:param error:error];
}
}
复制代码
从这个方法最后的switch语句可以看出,这个方法的作用是返回当前request
的NSURLSessionTask
的实例。而且最终生成NSURLSessionTask
实例的方法都是通过dataTaskWithHTTPMethod:requestSerializer:URLString:parameters:error:
这个私有方法来实现的。在讲解这个关键的私有方法之前,先来逐步讲解一下这个私有方法需要的每个参数的获取方法:
-
- 1>获得请求类型(GET,POST等)
//YTKNetworkAgent.m
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
...
YTKRequestMethod method = [request requestMethod];
...
}
复制代码
requestMethod
方法最初在YTKBaseRequest
里面已经实现了,默认返回了YTKRequestMethodGET
。
它的枚举类型在YTKBaseRequest.h
里面定义:
//YTKBaseRequest.h
/// HTTP Request method.
typedef NS_ENUM(NSInteger, YTKRequestMethod) {
YTKRequestMethodGET = 0,
YTKRequestMethodPOST,
YTKRequestMethodHEAD,
YTKRequestMethodPUT,
YTKRequestMethodDELETE,
YTKRequestMethodPATCH,
};
复制代码
用户可以根据实际的需求在自定义request
类里面重写这个方法:
//RegisterAPI.m
- (YTKRequestMethod)requestMethod {
return YTKRequestMethodPOST;
}
复制代码
-
- 2>获得请求url:
//YTKNetworkAgent.m
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
...
NSString *url = [self buildRequestUrl:request];
...
}
//返回当前请求url
- (NSString *)buildRequestUrl:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
//用户自定义的url(不包括在YTKConfig里面设置的base_url)
NSString *detailUrl = [request requestUrl];
NSURL *temp = [NSURL URLWithString:detailUrl];
// If detailUrl is valid URL
// 存在host和scheme的url立即返回正确
if (temp && temp.host && temp.scheme) {
return detailUrl;
}
// Filter URL if needed如果需要过滤,则过滤
NSArray *filters = [_config urlFilters];
for (id<YTKUrlFilterProtocol> f in filters) {
detailUrl = [f filterUrl:detailUrl withRequest:request];
}
NSString *baseUrl;
if ([request useCDN]) {
//如果使用CDN,在当前请求没有配置CDN地址的情况下,返回全局配置的CDN
if ([request cdnUrl].length > 0) {
baseUrl = [request cdnUrl];
} else {
baseUrl = [_config cdnUrl];
}
} else {
//如果使用baseUrl,在当前请求没有配置baseUrl,返回全局配置的baseUrl
if ([request baseUrl].length > 0) {
baseUrl = [request baseUrl];
} else {
baseUrl = [_config baseUrl];
}
}
// URL slash compability 如果末尾没有/,则在末尾添加一个/
NSURL *url = [NSURL URLWithString:baseUrl];
if (baseUrl.length > 0 && ![baseUrl hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
}
复制代码
-
- 3>获得请求参数
//YTKNetworkAgent.m
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
...
//获取用户提供的请求参数
id param = request.requestArgument;
//获取用户提供的构造请求体的block(默认是没有的)
AFConstructingBlock constructingBlock = [request constructingBodyBlock];
...
}
复制代码
在这里,requestArgument
是一个get方法,需要用户自己定义请求体,例如在RegisterAPI
里面就定义了两个请求参数:
//RegisterApi.m
- (id)requestArgument {
return @{
@"username": _username,
@"password": _password
};
}
复制代码
-
- 4>获得request serializer
//YTKNetworkAgent.m
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
...
//4. 获得request serializer
AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];
...
}
- (AFHTTPRequestSerializer *)requestSerializerForRequest:(YTKBaseRequest *)request {
AFHTTPRequestSerializer *requestSerializer = nil;
//HTTP or JSON
if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
requestSerializer = [AFHTTPRequestSerializer serializer];
} else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
requestSerializer = [AFJSONRequestSerializer serializer];
}
//超时时间
requestSerializer.timeoutInterval = [request requestTimeoutInterval];
//是否允许数据服务
requestSerializer.allowsCellularAccess = [request allowsCellularAccess];
// If api needs server username and password,如果当前请求需要验证
NSArray<NSString *> *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
if (authorizationHeaderFieldArray != nil) {
[requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFieldArray.firstObject
password:authorizationHeaderFieldArray.lastObject];
}
// If api needs to add custom value to HTTPHeaderField
//如果当前请求需要自定义HTTPHeaderField
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;
}
复制代码
上面这个方法通过传入的request
实例,根据它的一些配置(用户提供)来获取AFHTTPRequestSerializer
的实例。
到现在为止,获取NSURLSessionTask
实例的几个参数都拿到了,剩下的就是调用dataTaskWithHTTPMethod:requestSerializer:URLString:parameters:error:
方法来获取NSURLSessionTask
实例了。我们来看一下这个方法的具体实现:
//YTKNetworkAgent.m
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError * _Nullable __autoreleasing *)error {
return [self dataTaskWithHTTPMethod:method requestSerializer:requestSerializer URLString:URLString parameters:parameters constructingBodyWithBlock:nil error:error];
}
//最终返回NSURLSessionDataTask实例
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error {
NSMutableURLRequest *request = nil;
//根据有无构造请求体的block的情况来获取request
if (block) {
request = [requestSerializer multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:error];
} else {
request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:error];
}
//获得request以后来获取dataTask
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [_manager dataTaskWithRequest:request
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *_error) {
//响应的统一处理
[self handleRequestResult:dataTask responseObject:responseObject error:_error];
}];
return dataTask;
}
复制代码
这两个方法,上面的方法调用了下面的来获取最终的NSURLSessionDataTask
实例。
OK,现在我们已经知道了NSURLSessionDataTask
实例是如何获取的,再来看一下在addRequest:
方法里接下来做的是对序列化失败的处理:
//YTKNetworkAgent.m
- (void)addRequest:(YTKBaseRequest *)request {
...
//序列化失败
if (requestSerializationError) {
//请求失败的处理
[self requestDidFailWithRequest:request error:requestSerializationError];
return;
}
...
}
复制代码
requestDidFailWithRequest:方法专门处理请求失败的情况,因为它被包含在统一处理请求回调的方法中,所以在稍后会在讲解统一处理请求回调的方法的时候再详细讲解这个方法。
继续往下走,到了优先级的映射部分:
//YTKNetworkAgent.m
- (void)addRequest:(YTKBaseRequest *)request {
...
// 优先级的映射
// !!Available on iOS 8 +
if ([request.requestTask respondsToSelector:@selector(priority)]) {
switch (request.requestPriority) {
case YTKRequestPriorityHigh:
request.requestTask.priority = NSURLSessionTaskPriorityHigh;
break;
case YTKRequestPriorityLow:
request.requestTask.priority = NSURLSessionTaskPriorityLow;
break;
case YTKRequestPriorityDefault:
/*!!fall through*/
default:
request.requestTask.priority = NSURLSessionTaskPriorityDefault;
break;
}
}
...
}
复制代码
requestPriority
是YTKBaseRequest
的一个枚举属性,它的枚举在YTKBaseRequest.h
里面被定义:
typedef NS_ENUM(NSInteger, YTKRequestPriority) {
YTKRequestPriorityLow = -4L,
YTKRequestPriorityDefault = 0,
YTKRequestPriorityHigh = 4,
};
复制代码
在这里,将用户设置的YTKRequestPriority
映射到NSURLSessionTask的priority上
。
到这里,我们拿到了task的实例并设置好了优先级,紧接着就是addRequest:
方法里的第二个部分:
(2)把request放入专门用来保存请求的字典中,key为taskIdentifier:
//YTKNetworkAgent.m
- (void)addRequest:(YTKBaseRequest *)request {
...
...
//将request实例放入保存请求的字典中,taskIdentifier为key,request为值
[self addRequestToRecord:request];
...
}
- (void)addRequestToRecord:(YTKBaseRequest *)request {
//加锁
Lock();
_requestsRecord[@(request.requestTask.taskIdentifier)] = request;
Unlock();
}
#define Lock() pthread_mutex_lock(&_lock)
#define Unlock() pthread_mutex_unlock(&_lock)
复制代码
可以看到,在添加前和添加后是进行了加锁和解锁的处理的。而且request实例被保存的时候,将其task的identifier作为key来保存。 **(3)启动task **
//YTKNetworkAgent.m
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
...
[request.requestTask resume];
...
}
复制代码
那么接下来我们看一下YTKNetwork是如何处理请求的回调的。
眼尖的同学们可能会注意到,在获取NSURLSessionTask实例的时候,出现了两次“响应的统一处理”的注释,大家可以搜索这个注释就可以找到这个方法:handleRequestResult:responseObject:error:
。这个方法负责的是对请求回调的处理,当然包括了成功和失败的情况。我们来看一下在这个方法里都做了什么:
//YTKNetworkAgent.m
//统一处理请求结果,包括成功和失败的情况
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
//1. 获取task对应的request
Lock();
YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
Unlock();
//如果不存在对应的request,则立即返回
if (!request) {
return;
}
YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));
NSError * __autoreleasing serializationError = nil;
NSError * __autoreleasing validationError = nil;
NSError *requestError = nil;
BOOL succeed = NO;
//2. 获取request对应的response
request.responseObject = responseObject;
//3. 获取responseObject,responseData和responseString
if ([request.responseObject isKindOfClass:[NSData class]]) {
//3.1 获取 responseData
request.responseData = responseObject;
//3.2 获取responseString
request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
//3.3 获取responseObject(或responseJSONObject)
//根据返回的响应的序列化的类型来得到对应类型的响应
switch (request.responseSerializerType) {
case YTKResponseSerializerTypeHTTP:
// Default serializer. Do nothing.
break;
case YTKResponseSerializerTypeJSON:
request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
request.responseJSONObject = request.responseObject;
break;
case YTKResponseSerializerTypeXMLParser:
request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError];
break;
}
}
//4. 判断是否有错误,将错误对象赋值给requestError,改变succeed的布尔值。目的是根据succeed的值来判断到底是进行成功的回调还是失败的回调
if (error) {
//如果该方法传入的error不为nil
succeed = NO;
requestError = error;
} else if (serializationError) {
//如果序列化失败了
succeed = NO;
requestError = serializationError;
} else {
//即使没有error而且序列化通过,也要验证request json是否有效
succeed = [self validateResult:request error:&validationError];
requestError = validationError;
}
//5. 根据succeed的布尔值来调用相应的处理
if (succeed) {
//请求成功的处理
[self requestDidSucceedWithRequest:request];
} else {
//请求失败的处理
[self requestDidFailWithRequest:request error:requestError];
}
//6. 回调完成的处理
dispatch_async(dispatch_get_main_queue(), ^{
//6.1 在字典里移除当前request
[self removeRequestFromRecord:request];
//6.2 清除所有block
[request clearCompletionBlock];
});
}
复制代码
简单讲一下上面的代码:
-
- 首先通过
task
的identifier
值从YTKNetworkAgent
保存的字典里获取对应的请求。
- 首先通过
-
- 然后将获得的
responseObject
进行处理,将处理后获得的responseObject
,responseData
和responseString
赋值给当前的请求实例request
。
- 然后将获得的
-
- 再根据这些值的获取情况来判断最终回调的成败(改变
succeed
的值)。
- 再根据这些值的获取情况来判断最终回调的成败(改变
-
- 最后根据
succeed
的值来进行成功和失败的回调。
- 最后根据
这里先重点介绍一下是如何判断json的有效性的:
//YTKNetworkAgent.m
//判断code是否符合范围和json的有效性
- (BOOL)validateResult:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
//1. 判断code是否在200~299之间
BOOL result = [request statusCodeValidator];
if (!result) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidStatusCode userInfo:@{NSLocalizedDescriptionKey:@"Invalid status code"}];
}
return result;
}
//2. result 存在的情况判断json是否有效
id json = [request responseJSONObject];
id validator = [request jsonValidator];
if (json && validator) {
//通过json和validator来判断json是否有效
result = [YTKNetworkUtils validateJSON:json withValidator:validator];
//如果json无效
if (!result) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidJSONFormat userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON format"}];
}
return result;
}
}
return YES;
}
复制代码
在这里,首先,用statusCodeValidator方法判断响应的code是否在正确的范围:
//YTKBaseReqiest.m
- (BOOL)statusCodeValidator {
NSInteger statusCode = [self responseStatusCode];
return (statusCode >= 200 && statusCode <= 299);
}
- (NSInteger)responseStatusCode {
return self.response.statusCode;
}
复制代码
然后再判断json的格式有效性:
//YTKNetworkUtils.m
//判断json的有效性
+ (BOOL)validateJSON:(id)json withValidator:(id)jsonValidator {
if ([json isKindOfClass:[NSDictionary class]] &&
[jsonValidator isKindOfClass:[NSDictionary class]]) {
NSDictionary * dict = json;
NSDictionary * validator = jsonValidator;
BOOL result = YES;
NSEnumerator * enumerator = [validator keyEnumerator];
NSString * key;
while ((key = [enumerator nextObject]) != nil) {
id value = dict[key];
id format = validator[key];
if ([value isKindOfClass:[NSDictionary class]]
|| [value isKindOfClass:[NSArray class]]) {
result = [self validateJSON:value withValidator:format];
if (!result) {
break;
}
} else {
if ([value isKindOfClass:format] == NO &&
[value isKindOfClass:[NSNull class]] == NO) {
result = NO;
break;
}
}
}
return result;
} else if ([json isKindOfClass:[NSArray class]] &&
[jsonValidator isKindOfClass:[NSArray class]]) {
NSArray * validatorArray = (NSArray *)jsonValidator;
if (validatorArray.count > 0) {
NSArray * array = json;
NSDictionary * validator = jsonValidator[0];
for (id item in array) {
BOOL result = [self validateJSON:item withValidator:validator];
if (!result) {
return NO;
}
}
}
return YES;
} else if ([json isKindOfClass:jsonValidator]) {
return YES;
} else {
return NO;
}
}
复制代码
注意,YTKNetworkUtils
这个类是在YTKNetworkPirvate
里面定义的,YTKNetworkPirvate
里面有一些工具类的方法,在后面还会遇到。
在验证返回的JSON数据是否有效以后,就可以进行回调了:
//YTKNetworkAgent.m
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
...
//5. 根据succeed的布尔值来调用相应的处理
if (succeed) {
//请求成功的处理
[self requestDidSucceedWithRequest:request];
} else {
//请求失败的处理
[self requestDidFailWithRequest:request error:requestError];
}
//6. 回调完成的处理
dispatch_async(dispatch_get_main_queue(), ^{
//6.1 在字典里移除当前request
[self removeRequestFromRecord:request];
//6.2 清除所有block
[request clearCompletionBlock];
});
...
}
复制代码
-
- 请求成功的处理:
//YTKNetworkAgent.m
//请求成功:主要负责将结果写入缓存&回调成功的代理和block
- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
@autoreleasepool {
//写入缓存
[request requestCompletePreprocessor];
}
dispatch_async(dispatch_get_main_queue(), ^{
//告诉Accessories请求就要停止了
[request toggleAccessoriesWillStopCallBack];
//在真正的回调之前做的处理,用户自定义
[request requestCompleteFilter];
//如果有代理,则调用成功的代理
if (request.delegate != nil) {
[request.delegate requestFinished:request];
}
//如果传入了成功block回调的代码,则调用
if (request.successCompletionBlock) {
request.successCompletionBlock(request);
}
//告诉Accessories请求已经结束了
[request toggleAccessoriesDidStopCallBack];
});
}
复制代码
我么可以看到,在请求成功以后,第一个做的是写入缓存,我们来看一下requestCompletePreprocessor
方法的实现:
//YTKRequest.m
- (void)requestCompletePreprocessor {
[super requestCompletePreprocessor];
//是否异步将responseData写入缓存(写入缓存的任务放在专门的队列进行)
if (self.writeCacheAsynchronously) {
dispatch_async(ytkrequest_cache_writing_queue(), ^{
//写入缓存文件
[self saveResponseDataToCacheFile:[super responseData]];
});
} else {
//写入缓存文件
[self saveResponseDataToCacheFile:[super responseData]];
}
}
//写入缓存文件
- (void)saveResponseDataToCacheFile:(NSData *)data {
if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {
if (data != nil) {
@try {
// 1. 保存request的responseData到cacheFilePath
[data writeToFile:[self cacheFilePath] atomically:YES];
// 2. 保存request的metadata到cacheMetadataFilePath
YTKCacheMetadata *metadata = [[YTKCacheMetadata alloc] init];
metadata.version = [self cacheVersion];
metadata.sensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
metadata.stringEncoding = [YTKNetworkUtils stringEncodingWithRequest:self];
metadata.creationDate = [NSDate date];
metadata.appVersionString = [YTKNetworkUtils appVersionString];
[NSKeyedArchiver archiveRootObject:metadata toFile:[self cacheMetadataFilePath]];
} @catch (NSException *exception) {
YTKLog(@"Save cache failed, reason = %@", exception.reason);
}
}
}
}
复制代码
首先看一下写入缓存操作的执行条件:当cacheTimeInSeconds
方法返回大于0并且isDataFromCache
为NO的时候会进行写入缓存。
cacheTimeInSeconds
方法返回的是缓存保存的时间,它最初定义在YTKBaseRquest
里面,默认返回是-1:
//YTKBaseRequest.m
- (NSInteger)cacheTimeInSeconds {
return -1;
}
复制代码
所以说YTKNetwork默认是不进行缓存的,如果用户需要做缓存,则需要在自定义的request类里面返回一个大于0的整数,这个整数的单位是秒。
现在知道了YTKRequest的缓存内容,我们来看一下这两种缓存的位置:
//YTKRequest.m
//纯NSData数据缓存的文件名
- (NSString *)cacheFileName {
NSString *requestUrl = [self requestUrl];
NSString *baseUrl = [YTKNetworkConfig sharedConfig].baseUrl;
id argument = [self cacheFileNameFilterForRequestArgument:[self requestArgument]];
NSString *requestInfo = [NSString stringWithFormat:@"Method:%ld Host:%@ Url:%@ Argument:%@",
(long)[self requestMethod], baseUrl, requestUrl, argument];
NSString *cacheFileName = [YTKNetworkUtils md5StringFromString:requestInfo];
return cacheFileName;
}
//纯NSData数据的缓存位置
- (NSString *)cacheFilePath {
NSString *cacheFileName = [self cacheFileName];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheFileName];
return path;
}
//元数据的缓存位置
- (NSString *)cacheMetadataFilePath {
NSString *cacheMetadataFileName = [NSString stringWithFormat:@"%@.metadata", [self cacheFileName]];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheMetadataFileName];
return path;
}
//创建用户保存所有YTKNetwork缓存的文件夹
- (NSString *)cacheBasePath {
//获取全路径
NSString *pathOfLibrary = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [pathOfLibrary stringByAppendingPathComponent:@"LazyRequestCache"];
// YTKCacheDirPathFilterProtocol定义了用户可以自定义存储位置的代理方法
NSArray<id<YTKCacheDirPathFilterProtocol>> *filters = [[YTKNetworkConfig sharedConfig] cacheDirPathFilters];
if (filters.count > 0) {
for (id<YTKCacheDirPathFilterProtocol> f in filters) {
path = [f filterCacheDirPath:path withRequest:self];
}
}
//创建文件夹
[self createDirectoryIfNeeded:path];
return path;
}
复制代码
可以看出,纯NSData数据缓存的文件名包含了请求方法(GET,POST..),baseURL,requestURL,请求参数拼接的字符串再进行md5加密而成。
而元数据的的文件名则在纯NSData数据缓存的文件名后面加上了.metadata后缀。保存在沙盒/library/LazyRequestCache/...下。
-
- 现在我们知道了请求成功的处理,那么再来看一下请求失败时的处理:
//YTKNetworkAgent.m
//请求失败
- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error {
request.error = error;
YTKLog(@"Request %@ failed, status code = %ld, error = %@",
NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription);
// Save incomplete download data.
// 储存未完成的下载数据
NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
if (incompleteDownloadData) {
[incompleteDownloadData writeToURL:[self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] atomically:YES];
}
// Load response from file and clean up if download task failed.
//如果下载任务失败,则取出对应的响应文件并清空
if ([request.responseObject isKindOfClass:[NSURL class]]) {
NSURL *url = request.responseObject;
//isFileURL:是否是文件,如果是,则可以再isFileURL获取;&&后面是再次确认是否存在改url对应的文件
if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
//将url的data和string赋给request
request.responseData = [NSData dataWithContentsOfURL:url];
request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
}
//清空request
request.responseObject = nil;
}
@autoreleasepool {
//请求失败的预处理,YTK没有定义,需要用户定义
[request requestFailedPreprocessor];
}
dispatch_async(dispatch_get_main_queue(), ^{
//告诉Accessories请求就要停止了
[request toggleAccessoriesWillStopCallBack];
//在真正的回调之前做的处理
[request requestFailedFilter];
//如果有代理,就调用代理
if (request.delegate != nil) {
[request.delegate requestFailed:request];
}
//如果传入了失败回调的block代码,就调用block
if (request.failureCompletionBlock) {
request.failureCompletionBlock(request);
}
//告诉Accessories请求已经停止了
[request toggleAccessoriesDidStopCallBack];
});
}
复制代码
在这个方法里,首先判断了当前任务是否为下载任务,如果是,则储存当前已经下载好的data到resumableDownloadPath
里面。而如果下载任务失败,则将其对应的在本地保存的路径上的文件清空。
到这里,我已经把单个请求从配置,发送,响应,回调的步骤都讲解完了。为了帮助大家理解整个过程,这里提供了整个YTKNetwork的流程图如下:
我们说YTKNetworkAgent
是请求的发送者,既然有发送,也就会有取消等操作,这就不得不提它的另外两个接口:
//YTKNetworkAgent.h
/// 取消某个request
- (void)cancelRequest:(YTKBaseRequest *)request;
/// 取消所有添加的request
- (void)cancelAllRequests;
复制代码
首先我们看下取消某个request这个方法的实现:
//YTKNetworkAgent.m
/// 取消某个request
- (void)cancelRequest:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
//获取request的task,并取消
[request.requestTask cancel];
//从字典里移除当前request
[self removeRequestFromRecord:request];
//清理所有block
[request clearCompletionBlock];
}
//从字典里移除某request
- (void)removeRequestFromRecord:(YTKBaseRequest *)request {
//加锁
Lock();
[_requestsRecord removeObjectForKey:@(request.requestTask.taskIdentifier)];
YTKLog(@"Request queue size = %zd", [_requestsRecord count]);
Unlock();
}
复制代码
取消所有在字典里添加的request:
//YTKNetworkAgent.m
- (void)cancelAllRequests {
Lock();
NSArray *allKeys = [_requestsRecord allKeys];
Unlock();
if (allKeys && allKeys.count > 0) {
NSArray *copiedKeys = [allKeys copy];
for (NSNumber *key in copiedKeys) {
Lock();
YTKBaseRequest *request = _requestsRecord[key];
Unlock();
//stop每个请求
[request stop];
}
}
}
复制代码
这个stop方法是在YTKBaseRequest里面定义的:
//YTKBaseRequest.m
- (void)stop {
//告诉Accessories将要回调了
[self toggleAccessoriesWillStopCallBack];
//清空代理
self.delegate = nil;
//调用agent的取消某个request的方法
[[YTKNetworkAgent sharedAgent] cancelRequest:self];
//告诉Accessories回调完成了
[self toggleAccessoriesDidStopCallBack];
}
复制代码
以上OK,看到这里,相信你对YTKNetwork
单个请求的流程有了比较好的了解了,下面我们来看一下YTKNetwork
的高级功能:批量请求和链式请求。
4.2 批量请求YTKBatchRequest
用于方便地发送批量的网络请求,YTKBatchRequest
是一个容器类,它可以放置多个 YTKRequest
子类,并统一处理这多个网络请求的成功和失败。
4.2.1 批量请求的发起
在如下的示例中,我们发送 4个批量的请求,并统一处理这4个请求同时成功的回调。
#import "YTKBatchRequest.h"
#import "GetImageApi.h"
#import "GetUserInfoApi.h"
- (void)sendBatchRequest {
GetImageApi *a = [[GetImageApi alloc] initWithImageId:@"1.jpg"];
GetImageApi *b = [[GetImageApi alloc] initWithImageId:@"2.jpg"];
GetImageApi *c = [[GetImageApi alloc] initWithImageId:@"3.jpg"];
GetUserInfoApi *d = [[GetUserInfoApi alloc] initWithUserId:@"123"];
YTKBatchRequest *batchRequest = [[YTKBatchRequest alloc] initWithRequestArray:@[a, b, c, d]];
[batchRequest startWithCompletionBlockWithSuccess:^(YTKBatchRequest *batchRequest) {
NSLog(@"succeed");
NSArray *requests = batchRequest.requestArray;
GetImageApi *a = (GetImageApi *)requests[0];
GetImageApi *b = (GetImageApi *)requests[1];
GetImageApi *c = (GetImageApi *)requests[2];
GetUserInfoApi *user = (GetUserInfoApi *)requests[3];
// deal with requests result ...
} failure:^(YTKBatchRequest *batchRequest) {
NSLog(@"failed");
}];
}
复制代码
4.2.2 批量请求的源码解析
批量请求需要用一个含有YTKRequest
子类的数组来初始化,并将这个数组保存起来赋给它的_requestArray
实例变量:
//YTKBatchRequest.m
- (instancetype)initWithRequestArray:(NSArray<YTKRequest *> *)requestArray {
self = [super init];
if (self) {
//保存为属性
_requestArray = [requestArray copy];
//批量请求完成的数量初始化为0
_finishedCount = 0;
for (YTKRequest * req in _requestArray) {
//类型检查,所有元素都必须为YTKRequest或的它的子类,否则强制初始化失败
if (![req isKindOfClass:[YTKRequest class]]) {
YTKLog(@"Error, request item must be YTKRequest instance.");
return nil;
}
}
}
return self;
}
复制代码
初始化以后,我们就可以调用start
方法来发起当前YTKBatchRequest
实例所管理的所有请求了:
//YTKBatchRequest.m
//batch请求开始
- (void)startWithCompletionBlockWithSuccess:(void (^)(YTKBatchRequest *batchRequest))success
failure:(void (^)(YTKBatchRequest *batchRequest))failure {
[self setCompletionBlockWithSuccess:success failure:failure];
[self start];
}
//设置成功和失败的block
- (void)setCompletionBlockWithSuccess:(void (^)(YTKBatchRequest *batchRequest))success
failure:(void (^)(YTKBatchRequest *batchRequest))failure {
self.successCompletionBlock = success;
self.failureCompletionBlock = failure;
}
- (void)start {
//如果batch里第一个请求已经成功结束,则不能再start
if (_finishedCount > 0) {
YTKLog(@"Error! Batch request has already started.");
return;
}
//最开始设定失败的request为nil
_failedRequest = nil;
//使用YTKBatchRequestAgent来管理当前的批量请求
[[YTKBatchRequestAgent sharedAgent] addBatchRequest:self];
[self toggleAccessoriesWillStartCallBack];
//遍历所有request,并开始请求
for (YTKRequest * req in _requestArray) {
req.delegate = self;
[req clearCompletionBlock];
[req start];
}
}
复制代码
在这里,我们可以看出:
-
- 在至少完成了其中一个请求以后,调用当前
YTKBatchRequest
实例的start
方法会立即返回,否则可以无限制start
。
- 在至少完成了其中一个请求以后,调用当前
-
YTKBatchRequest
的实例是需要在发起请求之前,要被添加在YTKBatchRequestAgent
里的数组里:
//YTKBatchRequestAgent.m
- (void)addBatchRequest:(YTKBatchRequest *)request {
@synchronized(self) {
[_requestArray addObject:request];
}
}
复制代码
-
- 因为是批量发送请求,所以在这里是遍历
YTKBatchRequest
实例的_requestArray
并逐一发送请求。因为已经封装好了单个的请求,所以在这里直接start就好了。
- 因为是批量发送请求,所以在这里是遍历
发起请求以后,在每个请求回调的代理方法里,来判断这次批量请求是否成功。
YTKRequest
子类成功的回调:
- (void)requestFinished:(YTKRequest *)request {
//某个request成功后,首先让_finishedCount + 1
_finishedCount++;
//如果_finishedCount等于_requestArray的个数,则判定当前batch请求成功
if (_finishedCount == _requestArray.count) {
//调用即将结束的代理
[self toggleAccessoriesWillStopCallBack];
//调用请求成功的代理
if ([_delegate respondsToSelector:@selector(batchRequestFinished:)]) {
[_delegate batchRequestFinished:self];
}
//调用批量请求成功的block
if (_successCompletionBlock) {
_successCompletionBlock(self);
}
//清空成功和失败的block
[self clearCompletionBlock];
//调用请求结束的代理
[self toggleAccessoriesDidStopCallBack];
//从YTKBatchRequestAgent里移除当前的batch
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
}
复制代码
我们可以看到,在某个请求的回调成功以后,会让成功计数+1。在+1以后,如果成功计数和当前批量请求数组里元素的个数相等,则判定当前批量请求成功,并进行当前批量请求的成功回调。
接下来我们看一下某个请求失败的处理:
YTKRequest
子类失败的回调:
//YTKBatchRequest.m
- (void)requestFailed:(YTKRequest *)request {
_failedRequest = request;
//调用即将结束的代理
[self toggleAccessoriesWillStopCallBack];
// Stop
//停止batch里所有的请求
for (YTKRequest *req in _requestArray) {
[req stop];
}
// Callback
//调用请求失败的代理
if ([_delegate respondsToSelector:@selector(batchRequestFailed:)]) {
[_delegate batchRequestFailed:self];
}
//调用请求失败的block
if (_failureCompletionBlock) {
_failureCompletionBlock(self);
}
//清空成功和失败的block
[self clearCompletionBlock];
//调用请求结束的代理
[self toggleAccessoriesDidStopCallBack];
//从YTKBatchRequestAgent里移除当前的batch
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
复制代码
总的来说,YTKBatchRequest
类用一个数组来保存当前批量请求所要处理的所有request
实例。而且用一个成功计数来判定当前批量请求整体是否成功。而当前批量请求的失败则是由这些request
实例里面第一个失败的实例导致的:只要有一个request
回调失败了,则立即停止其他的所有请求并调用当前批量请求的失败回调。
4.3 链式请求YTKChainRequestAgent
4.3.1 链式请求的发起
用于管理有相互依赖的网络请求,它实际上最终可以用来管理多个拓扑排序后的网络请求。
以下是具体的代码示例,在示例中,我们在 sendChainRequest
方法中设置好了 Api 相互的依赖,然后。 我们就可以通过 chainRequestFinished
回调来处理所有网络请求都发送成功的逻辑了。如果有任何其中一个网络请求失败了,则会触发 chainRequestFailed
回调。
- (void)sendChainRequest {
RegisterApi *reg = [[RegisterApi alloc] initWithUsername:@"username" password:@"password"];
YTKChainRequest *chainReq = [[YTKChainRequest alloc] init];
[chainReq addRequest:reg callback:^(YTKChainRequest *chainRequest, YTKBaseRequest *baseRequest) {
RegisterApi *result = (RegisterApi *)baseRequest;
NSString *userId = [result userId];
GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId];
[chainRequest addRequest:api callback:nil];
}];
chainReq.delegate = self;
// start to send request
[chainReq start];
}
- (void)chainRequestFinished:(YTKChainRequest *)chainRequest {
// all requests are done
}
- (void)chainRequestFailed:(YTKChainRequest *)chainRequest failedBaseRequest:(YTKBaseRequest*)request {
// some one of request is failed
}
复制代码
4.3.1 链式请求的源码解析
和批量请求类似,处理链式请求的类是YTKChainRequest
,并且用YTKChainRequestAgent
单例来管理YTKChainRequest
的实例。
但是和批量请求不同的是,YTKChainRequest
实例的初始化是不需要传入一个含有request
的数组的:
//YTKChainRequest.m
- (instancetype)init {
self = [super init];
if (self) {
//下一个请求的index
_nextRequestIndex = 0;
//保存链式请求的数组
_requestArray = [NSMutableArray array];
//保存回调的数组
_requestCallbackArray = [NSMutableArray array];
//空回调,用来填充用户没有定义的回调block
_emptyCallback = ^(YTKChainRequest *chainRequest, YTKBaseRequest *baseRequest) {
// do nothing
};
}
return self;
}
复制代码
但是它提供了添加和删除request的接口:
//YTKChainRequest.m
//在当前chain添加request和callback
- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback {
//保存当前请求
[_requestArray addObject:request];
if (callback != nil) {
[_requestCallbackArray addObject:callback];
} else {
//之所以特意弄一个空的callback,是为了避免在用户没有给当前request的callback传值的情况下,造成request数组和callback数组的不对称
[_requestCallbackArray addObject:_emptyCallback];
}
}
复制代码
注意,在给YTKChainRequest
实例添加request
实例的同时,还可以传入回调的block。当然也可以不传,但是为了保持request数组和callback数组的对称性(因为回调的时候是需要根据request数组里的index来获取callback数组里对应的callback的),YTKNetwork
给我们提供了一个空的block。
我们接着看一下链式请求的发起:
//YTKChainRequest.m
- (void)start {
//如果第1个请求已经结束,就不再重复start了
if (_nextRequestIndex > 0) {
YTKLog(@"Error! Chain request has already started.");
return;
}
//如果请求队列数组里面还有request,则取出并start
if ([_requestArray count] > 0) {
[self toggleAccessoriesWillStartCallBack];
//取出当前request并start
[self startNextRequest];
//在当前的_requestArray添加当前的chain(YTKChainRequestAgent允许有多个chain)
[[YTKChainRequestAgent sharedAgent] addChainRequest:self];
} else {
YTKLog(@"Error! Chain request array is empty.");
}
}
复制代码
我们可以看到,YTKChainRequest
用_nextRequestIndex
来保存下一个请求的index,它的默认值是0。而它的值的累加是在当前请求结束后,发起下面的请求之前进行的。所以说,如果已经完成了请求队列里的第一个请求,就无法在启动当前的请求队列了,会立即返回。
这里startNextRequest
方法比较重要:在判断请求队列数组里面还有request
的话,就会调用这个方法:
//YTKChainRequest.m
- (BOOL)startNextRequest {
if (_nextRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[_nextRequestIndex];
_nextRequestIndex++;
request.delegate = self;
[request clearCompletionBlock];
[request start];
return YES;
} else {
return NO;
}
}
复制代码
这个方法有两个作用:
第一个作用是判断是否能进行下一个request
(如果index
大于或等于 request
数组的count
的话就不能在request
数组里取出request
,因为会造成数组越界) 第二个作用是如果可以进行下一个request
,则发起该request
。并将_nextRequestIndex+1
。
所以和批量请求不同的是,链式请求的请求队列是可以变动的,用户可以无限制地添加请求。只要请求队列里面有请求存在,则YTKChainRequest就会继续发送它们。
现在我们知道了YTKChainRequest的发送,接下来看一下回调部分:
和YTKBatchRequest相同的是,YTKChainRequest也实现了YTKRequest的代理:
//某个request请求成功的代理的实现
//YTKChainRequest.m
- (void)requestFinished:(YTKBaseRequest *)request {
//1. 取出当前的request和callback,进行回调
NSUInteger currentRequestIndex = _nextRequestIndex - 1;
YTKChainCallback callback = _requestCallbackArray[currentRequestIndex];
callback(self, request);//注意:这个回调只是当前request的回调,而不是当前chain全部完成的回调。当前chain的回调在下面
//2. 如果不能再继续请求了,说明当前成功的request已经是chain里最后一个request,也就是说当前chain里所有的回调都成功了,即这个chain请求成功了。
if (![self startNextRequest]) {
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(chainRequestFinished:)]) {
[_delegate chainRequestFinished:self];
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
}
[self toggleAccessoriesDidStopCallBack];
}
}
复制代码
我们可以看到,在某个request
回调成功以后,会根据当前请求的index(_nextRequestIndex-1)
来获取其对应的block并调用。接着,再调用startNextRequest
方法来判断当前的YTKChainRequest
的请求队列里面是否还有其他的请求了:
-
- 如果没有了,则调用当前
YTKChainRequest
的最终成功的回调。
- 如果没有了,则调用当前
-
- 如果还有,则发起接下来的
request
(按顺序)。 接下来我们再看一下某个request
失败的代理的实现:
- 如果还有,则发起接下来的
//YTKChainRequest.m
//某个reqeust请求失败的代理
- (void)requestFailed:(YTKBaseRequest *)request {
//如果当前 chain里的某个request失败了,则判定当前chain失败。调用当前chain失败的回调
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(chainRequestFailed:failedBaseRequest:)]) {
[_delegate chainRequestFailed:self failedBaseRequest:request];
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
}
[self toggleAccessoriesDidStopCallBack];
}
复制代码
如果当前的request
请求失败了,则判定当前链式请求是失败的,则立即调用当前链式请求的失败回调。
现在我们知道了链式请求的请求和回调,再来看一下链式请求的终止:
//YTKChainRequest.m
//终止当前的chain
- (void)stop {
//首先调用即将停止的callback
[self toggleAccessoriesWillStopCallBack];
//然后stop当前的请求,再清空chain里所有的请求和回掉block
[self clearRequest];
//在YTKChainRequestAgent里移除当前的chain
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
//最后调用已经结束的callback
[self toggleAccessoriesDidStopCallBack];
}
复制代码
这个stop
方法是可以在外部调用的,所以用户可以随时终止当前链式请求的进行。它首先调用clearReuqest
方法,将当前request
停止,再将请求队列数组和callback
数组清空。
//YTKChainRequest.m
- (void)clearRequest {
//获取当前请求的index
NSUInteger currentRequestIndex = _nextRequestIndex - 1;
if (currentRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[currentRequestIndex];
[request stop];
}
[_requestArray removeAllObjects];
[_requestCallbackArray removeAllObjects];
}
复制代码
5.结束
这篇是跟着J_Knight_
的YTKNetwork源码解析看了一遍源代码,然后记录学习大量摘抄过来的。
最后再附一遍原作者YTKNetwork源码解析 。