开源代码信息

开源地址

YTKNetwork是基于AFNetworking封装的iOS网络库,它在AFNetworking的基础上提供了如下的功能:

  • 支持按时间缓存网络请求内容
  • 支持按版本号缓存网络请求内容
  • 支持统一设置服务器和 CDN 的地址
  • 支持检查返回 JSON 内容的合法性
  • 支持文件的断点续传
  • 支持 block 和 delegate 两种模式的回调方式
  • 支持批量的网络请求发送,并统一设置它们的回调(实现在 YTKBatchRequest 类中)
  • 支持方便地设置有相互依赖的网络请求的发送,例如:发送请求 A,根据请求 A 的结果,选择性的发送请求 B 和 C,再根据 B 和 C 的结果,选择性的发送请求 D。(实现在 YTKChainRequest 类中)
  • 支持网络请求 URL 的 filter,可以统一为网络请求加上一些参数,或者修改一些路径。
  • 定义了一套插件机制,可以很方便地为 YTKNetwork 增加功能。猿题库官方现在提供了一个插件,可以在某些网络请求发起时,在界面上显示“正在加载”的 HUD。

源码分析

YTKNetWork 整个代码量不是很大,整个代码结构如下所示:

在整个请求过程中都是围绕着Request展开,这里需要注意的是YTKBatchRequest以及YTKChainRequest并不继承自YTKBaseRequest,它其实是通过请求数组requestArray来管理请求的形式,所以个人觉得这两个类的命名不是很合理,很容易让人以为它们和YTKRequest一样都是继承自YTKBaseRequest。另一个个人觉得不是很合理的地方是它将全部的数据都放到YTKRequest,会显得庞大且乱。

这篇博客将通过如下几个方面对YTKNetWork源码进行解析:

* YTKRequest && YTKBaseRequest 的组成
* 基本请求过程
* 批量请求
* 链式请求
* 缓存机制
* 断点续传
* URL filter
* 插件机制
1. YTKRequest && YTKBaseRequest 的组成

YTKRequest


@interface YTKBaseRequest : NSObject

#pragma mark - Request and Response Information
///=============================================================================
/// @name Request and Response Information
///=============================================================================

/// 当前请求执行所使用的NSURLSessionTask,在请求start之前不应该调用这个值,因为这时候这个值为nil
@property (nonatomic, strong, readonly) NSURLSessionTask *requestTask;
/// requestTask.currentRequest的快捷访问方式
@property (nonatomic, strong, readonly) NSURLRequest *currentRequest;
/// requestTask.originalRequest的快捷访问方式
@property (nonatomic, strong, readonly) NSURLRequest *originalRequest;
/// requestTask.response的快捷访问方式.
@property (nonatomic, strong, readonly) NSHTTPURLResponse *response;
/// 返回体状态码
@property (nonatomic, readonly) NSInteger responseStatusCode;
/// 头信息
@property (nonatomic, strong, readonly, nullable) NSDictionary *responseHeaders;
/// response的原始信息,在失败的时候值为nil
@property (nonatomic, strong, readonly, nullable) NSData *responseData;
/// response的字符串形式信息,在失败的时候值为nil
@property (nonatomic, strong, readonly, nullable) NSString *responseString;
/// 序列化后的结果对象,在下载的情景下并且在使用resumableDownloadPath的时候,这个值代表文件存放的地址
@property (nonatomic, strong, readonly, nullable) id responseObject;
/// JSON形式的结果
@property (nonatomic, strong, readonly, nullable) id responseJSONObject;
// 错误信息
@property (nonatomic, strong, readonly, nullable) NSError *error;
/// 当前请求是否被取消
@property (nonatomic, readonly, getter=isCancelled) BOOL cancelled;
/// 当前请求是否正在执行
@property (nonatomic, readonly, getter=isExecuting) BOOL executing;


#pragma mark - Request Configuration
///=============================================================================
/// @name Request Configuration
///=============================================================================

/// 用于标示一个请求的 tag
@property (nonatomic) NSInteger tag;
/// 用于存储关于请求的额外信息
@property (nonatomic, strong, nullable) NSDictionary *userInfo;
/// 请求代理,如果选择用block方式来交付数据可以忽略这个值
@property (nonatomic, weak, nullable) id<YTKRequestDelegate> delegate;
/// 请求成功后将会调用的Block,如果delegate以及block都指定都话会先调用delegate然后再调用block,并且这个block会在主线程调用
@property (nonatomic, copy, nullable) YTKRequestCompletionBlock successCompletionBlock;
/// 请求失败都时候调用的Block,规则同successCompletionBlock
@property (nonatomic, copy, nullable) YTKRequestCompletionBlock failureCompletionBlock;
/// 当前请求所添加的插件
@property (nonatomic, strong, nullable) NSMutableArray<id<YTKRequestAccessory>> *requestAccessories;
/// 用于POST形式下组装请求body的block
@property (nonatomic, copy, nullable) AFConstructingBlock constructingBodyBlock;
/// 这个用于断点续传的情况,用于表示下载的临时文件的存放路径。在请求start之前将会删除这些文件,如果下载成功文件将会自动下载到这个路径,否则下载结果将会保存到
/// responseData,responseString。
@property (nonatomic, strong, nullable) NSString *resumableDownloadPath;
/// 用于跟踪文件的下载进度
@property (nonatomic, copy, nullable) AFURLSessionTaskProgressBlock resumableDownloadProgressBlock;
/// 请求优先级
@property (nonatomic) YTKRequestPriority requestPriority;
/// 请求完成的回调block
- (void)setCompletionBlockWithSuccess:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure;
/// 清除回调完成后结果交付block
- (void)clearCompletionBlock;
/// 添加插件
- (void)addAccessory:(id<YTKRequestAccessory>)accessory;

#pragma mark - Request Action
///=============================================================================
/// @name Request Action
///=============================================================================

/// 开始请求
- (void)start;
/// 停止请求
- (void)stop;
/// 开始请求
- (void)startWithCompletionBlockWithSuccess:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure;

#pragma mark - Subclass Override
///=============================================================================
/// @name Subclass Override
///=============================================================================

/// 在后台获得到数据但是尚未切换到主线程之前调用这个方法。
- (void)requestCompletePreprocessor;
/// 在请求成功并且切换到主线程后调用这个方法
- (void)requestCompleteFilter;
/// 在后台请求失败但是尚未切换到主线程之前调用这个方法。
- (void)requestFailedPreprocessor;
/// 请求失败并且切换到主线程后调用这个方法
- (void)requestFailedFilter;
/// URL的host部分 比如 http://www.example.com.
- (NSString *)baseUrl;
/// 它可以有两种形式,一种是全路径形式,这时候就会忽略baseUrl直接使用requestUrl,
/// 另一种是这个值只包含url的路径信息比如/v1/user,这时候会将requestUrl与baseUrl进行拼接
- (NSString *)requestUrl;
/// cdn地址
- (NSString *)cdnUrl;
/// 请求的超时时间默认是60s
- (NSTimeInterval)requestTimeoutInterval;
/// 额外的请求参数,GET的时候添加到路径上,POST的时候添加到body
- (nullable id)requestArgument;
/// HTTP 请求方式
- (YTKRequestMethod)requestMethod;
/// 请求序列化类型
- (YTKRequestSerializerType)requestSerializerType;
/// 结构体序列化类型
- (YTKResponseSerializerType)responseSerializerType;
/// HTTP 认证参数数组
- (nullable NSArray<NSString *> *)requestAuthorizationHeaderFieldArray;
/// 额外的请求头
- (nullable NSDictionary<NSString *, NSString *> *)requestHeaderFieldValueDictionary;
/// 用于构建自定义请求,如果这个返回值为非空,那么上面所有用于构建请求的就全部被忽略,而统一使用这个请求
- (nullable NSURLRequest *)buildCustomUrlRequest;
/// 是否使用cdn
- (BOOL)useCDN;
/// 是否允许使用蜂窝网络
- (BOOL)allowsCellularAccess;
/// 用于校验responseJSONObject结构
- (nullable id)jsonValidator;
/// 用于校验responseStatusCode
- (BOOL)statusCodeValidator;

@end

YTKRequest

YTKRequest主要是在YTKBaseRequest添加了缓存的相关管理。

@interface YTKRequest : YTKBaseRequest

/// 是否忽略缓存结果,需要注意的是cacheTimeInSeconds默认值为-1所以如果我们没有明确给出一个cacheTimeInSeconds值的话实际上我们还是会忽略缓存
@property (nonatomic) BOOL ignoreCache;

/// 判断当前返回的数据是否是从缓存中获取而来
- (BOOL)isDataFromCache;

///手动从存储中加载缓存
- (BOOL)loadCacheWithError:(NSError * __autoreleasing *)error;

///以忽略缓存的形式启动请求,这个一般用于更新缓存数据
- (void)startWithoutCache;

// 缓存返回数据到缓存文件
- (void)saveResponseDataToCacheFile:(NSData *)data;

#pragma mark - Subclass Override

/// 缓存时间
- (NSInteger)cacheTimeInSeconds;

/// 缓存版本
- (long long)cacheVersion;

- (nullable id)cacheSensitiveData;

/// 是否异步写缓存
- (BOOL)writeCacheAsynchronously;

@end

2. 基本请求过程

基本请求这块代码都放在YTKBaseRequest以及YTKNetworkAgent中,YTKBaseRequest会将实际的处理封装在YTKNetworkAgent,我们就来看下这部分代码:

请求的发起

一般请求我们会通过startWithCompletionBlockWithSuccess来执行:

- (void)startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
//设置successBlock 以及failureBlock
[self setCompletionBlockWithSuccess:success failure:failure];
//启动请求
[self start];
}

- (void)start {
//通知插件将要开始请求
[self toggleAccessoriesWillStartCallBack];
//将请求添加到YTKNetworkAgent
[[YTKNetworkAgent sharedAgent] addRequest:self];
}

请求过程中会先设置成功,失败结果返回的block,然后通知插件请求将要开始了,插件可以根据实际需求做对应处理,比如显示加载动画等,然后就将请求传递给YTKNetworkAgent。

- (void)addRequest:(YTKBaseRequest *)request {

NSError * __autoreleasing requestSerializationError = nil;
//如果请求中有自定义的url则使用自定义的NSURLRequest创建对应的NSURLSessionDataTask
NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
if (customUrlRequest) {
__block NSURLSessionDataTask *dataTask = nil;
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 {
//直接通过sessionTaskForRequest获取NSURLSessionDataTask
request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
}

if (requestSerializationError) {
[self requestDidFailWithRequest:request error:requestSerializationError];
return;
}

// 设置请求的优先级
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
[self addRequestToRecord:request];
//启动请求
[request.requestTask resume];
}

在YTKNetworkAgent中我们首先会看下是否开发者在对应等request中重写了buildCustomUrlRequest方法,如果是的话则通过这个返回的request 生成NSURLSessionDataTask,否则使用传入的request通过调用sessionTaskForRequest生成NSURLSessionDataTask,紧接着给请求设置优先级,并且保持请求引用。然后开启请求。


- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {

// 取出请求方法
YTKRequestMethod method = [request requestMethod];
// 构建URL
NSString *url = [self buildRequestUrl:request];
// 准备请求参数
id param = request.requestArgument;
// 构建POST请求体Block
AFConstructingBlock constructingBlock = [request constructingBodyBlock];
// 通过请求来构建请求序列化器
AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];

//通过请求方法来构建出不同的NSURLSessionTask
switch (method) {
case YTKRequestMethodGET:
if (request.resumableDownloadPath) {
return [self downloadTaskWithDownloadPath:request.resumableDownloadPath
requestSerializer:requestSerializer
URLString:url
parameters:param
progress:request.resumableDownloadProgressBlock
error:error];
} else {
return [self dataTaskWithHTTPMethod:@"GET"
requestSerializer:requestSerializer
URLString:url
parameters:param
error:error];
}
case YTKRequestMethodPOST:
return [self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameters:param constructingBodyWithBlock:constructingBlock error:error];
case YTKRequestMethodHEAD:
return [self dataTaskWithHTTPMethod:@"HEAD" requestSerializer:requestSerializer URLString:url parameters:param error:error];
case YTKRequestMethodPUT:
return [self dataTaskWithHTTPMethod:@"PUT" requestSerializer:requestSerializer URLString:url parameters:param error:error];
case YTKRequestMethodDELETE:
return [self dataTaskWithHTTPMethod:@"DELETE" requestSerializer:requestSerializer URLString:url parameters:param error:error];
case YTKRequestMethodPATCH:
return [self dataTaskWithHTTPMethod:@"PATCH" requestSerializer:requestSerializer URLString:url parameters:param error:error];
}
}

在sessionTaskForRequest方法会将YTKBaseRequest中的一系列配置拆开,通过在YTKBaseRequest中的配置来创建不同请求方法下的dataTask。首先看下请求url的构建策略:

请求url的构建是通过buildRequestUrl来实现的


- (NSString *)buildRequestUrl:(YTKBaseRequest *)request {

//获取请求的requestUrl使用它来构建出一个NSURL对象
NSString *detailUrl = [request requestUrl];
NSURL *temp = [NSURL URLWithString:detailUrl];
// 检查构建出来的URL是否是完整的URL请求
if (temp && temp.host && temp.scheme) {
return detailUrl;
}
// 对构建出来的URL通过Config中预设的urlFilters 一一对它进行处理,比如添加公共字段等
NSArray *filters = [_config urlFilters];
for (id<YTKUrlFilterProtocol> f in filters) {
detailUrl = [f filterUrl:detailUrl withRequest:request];
}

//构建baseUrl 如果使用CDN,并且cdnUrl有值,则baseUrl为cdnUrl,
//如果不使用CDN则优先使用当前request的baseUrl否则使用公共配置中的baseUrl
NSString *baseUrl;
if ([request useCDN]) {
if ([request cdnUrl].length > 0) {
baseUrl = [request cdnUrl];
} else {
baseUrl = [_config cdnUrl];
}
} else {
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:@""];
}
//将当前的baseUrl与detailUrl进行拼接
return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
}

首先会先取requestUrl看下是不是一个完整的URL请求,如果是的话就直接将这个作为请求返回,紧接着会从YTKNetWorkConfig中获取预设的urlFilters,一一对它进行处理,比如添加公共字段等。最终形成一个处理后的不包括scheme,host的URL。

接下来就是构建baseUrl的过程,这个顺序如下:

request --> cdnUrl  >  _config ---> cdnUrl  > request---> baseUrl > _config ---> baseUrl

总结来说就是能使用cdn的优先使用cdn的地址,不能使用的时候再选用baseUrl中的地址,能使用request的优先使用request的,没有的话使用config中的。最后将detailUrl和baseUrl进行拼接形成最终的地址。所以整个地址包含三个部分,一个是baseUrl,一个是requestUrl,然后是各个filter添加的信息。

接下来看下dataTask的创建这里分成三类,一个是GET方式,一个是POST方式,一个是download方式:

GET

[self dataTaskWithHTTPMethod:@"GET" requestSerializer:requestSerializer URLString:url parameters:param error:error];

POST

[self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameters:param constructingBodyWithBlock:constructingBlock error:error];

DOWNLOAD

[self downloadTaskWithDownloadPath:request.resumableDownloadPath
requestSerializer:requestSerializer
URLString:url parameters:param
progress:request.resumableDownloadProgressBlock
error:error];

上面GET POST 最终都走到下面的方法中,无非就是constructingBodyWithBlock空与非空的区别,

- (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;

//如果constructingBodyWithBlock非空的话使用multipartFormRequestWithMethod
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];
}
__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;
}

constructingBodyWithBlock有值的时候我们会使用请求序列化中的multipartFormRequestWithMethod进行处理,如果无值的话就会调用请求序列化器中的requestWithMethod进行处理。对请求处理后就可以使用该请求对象生成dataTask了,这部分没什么特殊的地方,我们关键来看下下载的部分:

- (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadPath
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
error:(NSError * _Nullable __autoreleasing *)error {
// add parameters to URL;
NSMutableURLRequest *urlRequest = [requestSerializer requestWithMethod:@"GET" URLString:URLString parameters:parameters error:error];

NSString *downloadTargetPath;
BOOL isDirectory;
if(![[NSFileManager defaultManager] fileExistsAtPath:downloadPath isDirectory:&isDirectory]) {
isDirectory = NO;
}
// If targetPath is a directory, use the file name we got from the urlRequest.
// Make sure downloadTargetPath is always a file, not directory.
// 构建下载路径downloadTargetPath
if (isDirectory) {
//如果给定的downloadpath是目录路径,那么将会将下载url下的最后部分作为文件名添加到最后,形成最终的下载文件地址
NSString *fileName = [urlRequest.URL lastPathComponent];
downloadTargetPath = [NSString pathWithComponents:@[downloadPath, fileName]];
} else {
downloadTargetPath = downloadPath;
}

// 在下载前如果文件存在先直接删除? 直接删除不先下到缓存再?
if ([[NSFileManager defaultManager] fileExistsAtPath:downloadTargetPath]) {
[[NSFileManager defaultManager] removeItemAtPath:downloadTargetPath error:nil];
}

BOOL resumeSucceeded = NO;
__block NSURLSessionDownloadTask *downloadTask = nil;
//未下载完成存储路径
NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:downloadPath];
if (localUrl != nil) {
// 是否存在未下载完成的文件
BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:localUrl.path];
// 加载数据
NSData *data = [NSData dataWithContentsOfURL:localUrl];
// 数据是否合法
BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data];

//是否可以恢复
BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid;
// Try to resume with resumeData.
// Even though we try to validate the resumeData, this may still fail and raise excecption.
if (canBeResumed) {
@try {
downloadTask = [_manager downloadTaskWithResumeData:data progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
} completionHandler:
^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
[self handleRequestResult:downloadTask responseObject:filePath error:error];
}];
resumeSucceeded = YES;
} @catch (NSException *exception) {
YTKLog(@"Resume download failed, reason = %@", exception.reason);
resumeSucceeded = NO;
}
}
}
//重新开始下载
if (!resumeSucceeded) {
downloadTask = [_manager downloadTaskWithRequest:urlRequest progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
} completionHandler:
^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
[self handleRequestResult:downloadTask responseObject:filePath error:error];
}];
}
return downloadTask;
}
  1. 首先会先通过判断传进来的downloadPath是否是目录名称,如果是则将url最后的部分作为文件名添加到path最后,如果是文件名,则直接作为目标下载地址
  2. 紧接着如果要下载的文件已经存在那么在下载之前先删除这个文件
  3. 然后再去临时目录去看是否有之前下载一半没有下载结束的缓存文件,临时目录下的这些缓存文件命名规则大家可以看下这部分代码:
- (NSURL *)incompleteDownloadTempPathForDownloadPath:(NSString *)downloadPath {
NSString *tempPath = nil;
//通过下载路径生成MD5字符串 最终生成 temp路径 + Incomplete + MD5[downloadPath]
NSString *md5URLString = [YTKNetworkUtils md5StringFromString:downloadPath];
tempPath = [[self incompleteDownloadTempCacheFolder] stringByAppendingPathComponent:md5URLString];
return tempPath == nil ? nil : [NSURL fileURLWithPath:tempPath];
}
  1. 如果有上一次下载一半的数据,就先加载到内存,验证数据的合法性,如果数据存在,并且合法,就通过downloadTaskWithResumeData创建dataTask,否则通过downloadTaskWithRequest来创建。

请求结束处理

请求结束统一调用handleRequestResult进行处理:

- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {

//取出请求
Lock();
YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
Unlock();

// When the request is cancelled and removed from records, the underlying
// AFNetworking failure callback will still kicks in, resulting in a nil `request`.
//
// Here we choose to completely ignore cancelled tasks. Neither success or failure
// callback will be called.
if (!request) {
return;
}

YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));

NSError * __autoreleasing serializationError = nil;
NSError * __autoreleasing validationError = nil;

NSError *requestError = nil;
BOOL succeed = NO;

//将结果传递给responseObject
request.responseObject = responseObject;

//通过对应的response序列化器进行处理
if ([request.responseObject isKindOfClass:[NSData class]]) {

request.responseData = responseObject;
request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];

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;
}
}
if (error) {
succeed = NO;
requestError = error;
} else if (serializationError) {
succeed = NO;
requestError = serializationError;
} else {
succeed = [self validateResult:request error:&validationError];
requestError = validationError;
}

//通知到外部
if (succeed) {
[self requestDidSucceedWithRequest:request];
} else {
[self requestDidFailWithRequest:request error:requestError];
}

dispatch_async(dispatch_get_main_queue(), ^{
//清除成功失败回调
[self removeRequestFromRecord:request];
[request clearCompletionBlock];
});
}

根据上面加载的请求发起的流程来看,不难猜出下载结束后的工作应该是,将请求数据通过response 序列化器进行处理后存放到YTKBaseRequest中,然后通过对应的block以及delegte将数据交付到应用层,然后将delegate以及block清除,但是我们知道请求返回后AFNetWorking实际上返回的是task,responseOject,这时候并不知道具体是哪个YTKBaseRequest,这个问题是怎么解决的呢?我们知道在addRequest的时候会调用****[self addRequestToRecord:request]****

- (void)addRequestToRecord:(YTKBaseRequest *)request {
Lock();
_requestsRecord[@(request.requestTask.taskIdentifier)] = request;
Unlock();
}

它会以dataTask的taskIdentifier为key YTKBaseRequest为value建立一个映射表,当请求结束的时候我们得到的是一个dataTask,那么就可以通过它的taskIdentifier作为key,获得对应的YTKBaseRequest,然后将resonse序列化器处理后的结果塞到YTKBaseRequest中。这一切都结束后再将映射关系从映射表中删除。

我们再继续看下请求成功和失败后会怎么处理接下来的流程:


- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
@autoreleasepool {
[request requestCompletePreprocessor];
}
dispatch_async(dispatch_get_main_queue(), ^{

//通过外部的插件将要停止请求
[request toggleAccessoriesWillStopCallBack];
[request requestCompleteFilter];

// 通过代理通知请求已经结束
if (request.delegate != nil) {
[request.delegate requestFinished:request];
}
// 通过block通知请求已经结束
if (request.successCompletionBlock) {
request.successCompletionBlock(request);
}
//通知外部的插件已经结束
[request toggleAccessoriesDidStopCallBack];
});
}

请求成功后在还没切回主线程之前会先调用requestCompletePreprocessor,在这里相当于埋下一个勾子,外面可以在这里实现类似统计之类的功能,紧接着切换到主线程,通知外部插件请求将要结束了,然后再调用requestCompleteFilter,这个和requestCompletePreprocessor功能类似,区别是一个在后台线程一个在主线程,紧接着就是通过代理和block将数据交付给上层,最后通知插件该请求结束。

- (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);

// 从userInfo 中取出下载没完成的数据
NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
NSURL *localUrl = nil;
if (request.resumableDownloadPath) {
localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
}
//存放到指定的路径
if (incompleteDownloadData && localUrl != nil) {
[incompleteDownloadData writeToURL:localUrl atomically:YES];
}

// 将文件从url中取出,如果是文件的话就删除下载失败的文件
if ([request.responseObject isKindOfClass:[NSURL class]]) {
NSURL *url = request.responseObject;
if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
request.responseData = [NSData dataWithContentsOfURL:url];
request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
}
request.responseObject = nil;
}

@autoreleasepool {
[request requestFailedPreprocessor];
}

//在主线程通过代理,block通知外部下载失败
dispatch_async(dispatch_get_main_queue(), ^{
[request toggleAccessoriesWillStopCallBack];
[request requestFailedFilter];

if (request.delegate != nil) {
[request.delegate requestFailed:request];
}
if (request.failureCompletionBlock) {
request.failureCompletionBlock(request);
}
[request toggleAccessoriesDidStopCallBack];
});
}

请求失败的话大体上和请求成功在流程上是一致的,只不过会在失败的时候通过****error.userInfo[NSURLSessionDownloadTaskResumeData]****拿到已经获取的数据,这时候就可以将这数据存储起来供下一次继续请求的时候实现断点续传。

最后看下取消请求的流程:

- (void)cancelRequest:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);

//如果有指定恢复下载文件的路径则取消后将数据写到恢复下载文件的地方
if (request.resumableDownloadPath && [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] != nil) {
NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
[requestTask cancelByProducingResumeData:^(NSData *resumeData) {
NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
[resumeData writeToURL:localUrl atomically:YES];
}];
} else {
//直接调用NSURLSessionDataTask cancel
[request.requestTask cancel];
}
[self removeRequestFromRecord:request];
//清除请求成功失败的block
[request clearCompletionBlock];
}

和请求失败处理一样可以做临时数据缓存,只不过需要通过NSURLSessionDownloadTask 的 cancelByProducingResumeData方法拿到已经获取的数据。再将数据写到临时目录下。

3. 批量请求

批量请求没啥需要介绍的,就是用一个数组将YTKRequst存放起来,在start的时候一个个调用YTKRequest的start方法:

- (void)start {
if (_finishedCount > 0) {
YTKLog(@"Error! Batch request has already started.");
return;
}
_failedRequest = nil;
[[YTKBatchRequestAgent sharedAgent] addBatchRequest:self];
[self toggleAccessoriesWillStartCallBack];
for (YTKRequest * req in _requestArray) {
req.delegate = self;
[req clearCompletionBlock];
[req start];
}
}
4. 链式请求

链式请求是一系列有依赖关系的请求,它会在某个请求执行完毕后,进行下一个请求的启动。接下来我们以添加请求启动请求请求结束后启动下一个请求三个部分进行简单展开:

1. 添加请求

- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback {
[_requestArray addObject:request];
if (callback != nil) {
[_requestCallbackArray addObject:callback];
} else {
[_requestCallbackArray addObject:_emptyCallback];
}
}

添加请求是通过addRequest:callback方法来完成的,每个请求与callback是一一对应的,如果外面传进来一个nil callback,那么_requestCallbackArray就会添加一个空请求,作为占位。

2. 启动请求

- (void)start {
if (_nextRequestIndex > 0) {
YTKLog(@"Error! Chain request has already started.");
return;
}
if ([_requestArray count] > 0) {
[self toggleAccessoriesWillStartCallBack];
[self startNextRequest];
[[YTKChainRequestAgent sharedAgent] addChainRequest:self];
} else {
YTKLog(@"Error! Chain request array is empty.");
}
}

- (BOOL)startNextRequest {
if (_nextRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[_nextRequestIndex];
_nextRequestIndex++;
request.delegate = self;
[request clearCompletionBlock];
[request start];
return YES;
} else {
return NO;
}
}

这个会按照请求数组的顺序一个一个取出来,调用start方法,这个就不做过多介绍了。

请求结束后启动下一个请求

- (void)requestFinished:(YTKBaseRequest *)request {
NSUInteger currentRequestIndex = _nextRequestIndex - 1;
YTKChainCallback callback = _requestCallbackArray[currentRequestIndex];
callback(self, request);
if (![self startNextRequest]) {
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(chainRequestFinished:)]) {
[_delegate chainRequestFinished:self];
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
}
[self toggleAccessoriesDidStopCallBack];
}
}

请求结束后会从_requestCallbackArray中获取对应的YTKChainCallback进行处理,然后调用startNextRequest开启下一个请求。

5. 缓存机制

前面已经提到YTKNetWork的缓存机制都保存在YTKRequest中,所以这部分我们来看下YTKReques:

依旧从启动请求开始:

- (void)start {
if (self.ignoreCache) {
[self startWithoutCache];
return;
}

// Do not cache download request.
if (self.resumableDownloadPath) {
[self startWithoutCache];
return;
}

if (![self loadCacheWithError:nil]) {
[self startWithoutCache];
return;
}

_dataFromCache = YES;

dispatch_async(dispatch_get_main_queue(), ^{
[self requestCompletePreprocessor];
[self requestCompleteFilter];
YTKRequest *strongSelf = self;
[strongSelf.delegate requestFinished:strongSelf];
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
});
}

在启动的时候会首先根据当前情况选择是否使用缓存数据,如果我们在YTKRequest中配置了ignoreCache,或者在下载数据的情景中并且配置了resumableDownloadPath,再或者缓存加载失败都会启动无缓存请求模式,
除了上述情况会先通过*loadCacheWithError**将缓存再本地的数据加载到内存中:

- (BOOL)loadCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// 检查缓存时间
if ([self cacheTimeInSeconds] < 0) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheTime userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache time"}];
}
return NO;
}

// 加载缓存信息数据
if (![self loadCacheMetadata]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidMetadata userInfo:@{ NSLocalizedDescriptionKey:@"Invalid metadata. Cache may not exist"}];
}
return NO;
}

// 检查缓存数据
if (![self validateCacheWithError:error]) {
return NO;
}

// 加载缓存
if (![self loadCacheData]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheData userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache data"}];
}
return NO;
}

return YES;
}

在加载数据之前会先检查下我们配置的缓存时间,如果cacheTimeInSeconds小于0的话就不再继续加载了,这时候会抛出错误。缓存数据加载失败走正常数据请求流程。否则再紧接着缓存加载的流程:

加载缓存信息数据loadCacheMetadata,检查缓存数据的合法性validateCacheWithError,加载数据loadCacheData,下面就针对这些环节进行介绍:

loadCacheMetadata

在加载缓存数据之前会先加载缓存的信息文件

- (BOOL)loadCacheMetadata {
NSString *path = [self cacheMetadataFilePath];
NSFileManager * fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
@try {
_cacheMetadata = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return YES;
} @catch (NSException *exception) {
YTKLog(@"Load cache metadata failed, reason = %@", exception.reason);
return NO;
}
}
return NO;
}

那么缓存信息文件存在哪里呢?我们怎么拿现有的配置取到缓存的信息文件?带着这个疑问继续看下面的代码:

- (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;
}

- (NSString *)cacheMetadataFilePath {
NSString *cacheMetadataFileName = [NSString stringWithFormat:@"%@.metadata", [self cacheFileName]];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheMetadataFileName];
return path;
}

- (NSString *)cacheBasePath {
NSString *pathOfLibrary = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [pathOfLibrary stringByAppendingPathComponent:@"LazyRequestCache"];

// Filter cache base path
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;
}


我们先来看下requestInfo这个字符串是怎么来的:

[NSString stringWithFormat:@"Method:%ld Host:%@ Url:%@ Argument:%@",(long)[self requestMethod], baseUrl, requestUrl, argument];

requestInfo包含着Method,URL(Host:Url),Argument,也就是说缓存文件信息文件名里面包含着一个请求的全部信息。在通过对这个字符串进行md5加密,生成的字符串作为缓存信息文件名。
最终文件存放在:

/[系统缓存路径]/LazyRequestCache[这个名称会经过cacheDirPathFilters进行处理,默认是LazyRequestCache]/xxxxxxxxxxxx.metadata

可以通过cacheDirPathFilters添加版本信息等公共的处理。

加载出来的对象结构如下:


@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;
@property (nonatomic, strong) NSString *appVersionString;

@end

包括缓存版本,关键字段,字符编码格式,创建时间,app版本信息。后面将会重点看下怎么利用这些信息来实现缓存策略。

validateCacheWithError

上面已经成功地加载了缓存信息文件,接下来就是对这些进行缓存信息进行校验了:

- (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
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;
}

首先是对缓存是否过期进行校验,这一步主要主要通过YTKCacheMetadata中的creationDate与当前时间计算差值,再与cacheTimeInSeconds对比判断缓存数据是否过期。
紧接着看缓存版本和我们request中的cacheVersion是否匹配。紧接着对比缓存的关键信息。然后再是应用版本的匹配,这些信息都匹配的时候才说明缓存是可用的,才会去加载缓存数据。

loadCacheData

紧接着会将本地缓存文件根据序列化形式加载到****_cacheData_cacheString_cacheXML****,

- (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;
}

向应用层交付缓存数据

dispatch_async(dispatch_get_main_queue(), ^{
[self requestCompletePreprocessor];
[self requestCompleteFilter];
YTKRequest *strongSelf = self;
[strongSelf.delegate requestFinished:strongSelf];
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
});

这里照样会通过delegate以及block进行交付,但是刚开始会感到奇怪我们刚刚加载数据的时候是加载到****_cacheString这些地方,但是这里交付却是strongSelf****,那么我们通过strongSelf的responseString这些对象访问的时候怎么转到_cacheString中的呢?

- (NSString *)responseString {
if (_cacheString) {
return _cacheString;
}
return [super responseString];
}

嗯,就是在getter方法中拦截,所以及时重置_cacheString非常关键。

缓存数据

还有最后一个问题?这些缓存数据是怎么来的呢?


- (void)requestCompletePreprocessor {

[super requestCompletePreprocessor];
if (self.writeCacheAsynchronously) {
dispatch_async(ytkrequest_cache_writing_queue(), ^{
[self saveResponseDataToCacheFile:[super responseData]];
});
} else {
[self saveResponseDataToCacheFile:[super responseData]];
}
}

我们知道请求结束的时候会调用requestCompletePreprocessor 这时候会调用 *saveResponseDataToCacheFile**。

- (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);
}
}
}
}

在saveResponseDataToCacheFile方法中会将服务端返回的数据写到缓存目录下,然后再更新缓存信息。

6. 断点续传

断点续传其实上面已经介绍过了,既然这里已经将它拎出来了,就做个总结把:

这里最关键在于两个方面:
请求中断的时候怎么缓存数据,请求开始的时候怎么续传:

YTKNetWork中会在请求失败,以及取消请求的时候对数据进行缓存:

失败时候进行缓存

- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error {

//......

NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
NSURL *localUrl = nil;
if (request.resumableDownloadPath) {
localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
}

if (incompleteDownloadData && localUrl != nil) {
[incompleteDownloadData writeToURL:localUrl atomically:YES];
}

//........
}

取消请求后的缓存


- (void)cancelRequest:(YTKBaseRequest *)request {

//如果有指定恢复下载文件的路径则取消后将数据写到恢复下载文件的地方
if (request.resumableDownloadPath && [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] != nil) {
NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
[requestTask cancelByProducingResumeData:^(NSData *resumeData) {
NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
[resumeData writeToURL:localUrl atomically:YES];
}];
} else {
//直接调用NSURLSessionDataTask cancel
[request.requestTask cancel];
}
[self removeRequestFromRecord:request];
//清除请求成功失败的block
[request clearCompletionBlock];
}

启动请求之前会先查看下载缓存地址,如果有数据则通过downloadTaskWithResumeData继续:

- (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadPath
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
error:(NSError * _Nullable __autoreleasing *)error {
// ......
// 在下载前如果文件存在先直接删除
if ([[NSFileManager defaultManager] fileExistsAtPath:downloadTargetPath]) {
[[NSFileManager defaultManager] removeItemAtPath:downloadTargetPath error:nil];
}

BOOL resumeSucceeded = NO;
__block NSURLSessionDownloadTask *downloadTask = nil;
//未下载完成存储路径
NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:downloadPath];
if (localUrl != nil) {
// 是否存在未下载完成的文件
BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:localUrl.path];
// 加载数据
NSData *data = [NSData dataWithContentsOfURL:localUrl];
// 数据是否合法
BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data];
//是否可以恢复
BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid;
// Try to resume with resumeData.
// Even though we try to validate the resumeData, this may still fail and raise excecption.
if (canBeResumed) {
@try {
downloadTask = [_manager downloadTaskWithResumeData:data progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
} completionHandler:
^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
[self handleRequestResult:downloadTask responseObject:filePath error:error];
}];
resumeSucceeded = YES;
} @catch (NSException *exception) {
YTKLog(@"Resume download failed, reason = %@", exception.reason);
resumeSucceeded = NO;
}
}
}

//......

return downloadTask;
}

7. Filter

在YTKNetworkConfig中有两个Filter,一个是_urlFilters,一个是_cacheDirPathFilters

@implementation YTKNetworkConfig {
NSMutableArray<id<YTKUrlFilterProtocol>> *_urlFilters;
NSMutableArray<id<YTKCacheDirPathFilterProtocol>> *_cacheDirPathFilters;
}

url filter它会对所有发起请求的requestUrl进行处理:

- (NSString *)buildRequestUrl:(YTKBaseRequest *)request {

//.......

// 对构建出来的URL通过Config中预设的urlFilters 一一对它进行处理,比如添加公共字段等
NSArray *filters = [_config urlFilters];
for (id<YTKUrlFilterProtocol> f in filters) {
detailUrl = [f filterUrl:detailUrl withRequest:request];
}

//......
}

cacheDirPathFilters filter会在创建缓存路径的时候调用:

- (NSString *)cacheBasePath {
NSString *pathOfLibrary = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [pathOfLibrary stringByAppendingPathComponent:@"LazyRequestCache"];

// Filter cache base path
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;
}
8. 插件机制

插件机制,这个在很多场合都会使用,其实就是一种观察者模式,将观察者添加到要观察对象的通知列表中,一旦某个关键环节发生了,就便利这些观察者,调用他们的方法通知他们。这时候观察者就可以插入自己想要的操作:
YTKNetWork中目前有三个类支持插件机制,分别是YTKBaseRequestYTKBatchRequestYTKChainRequest


@interface YTKBaseRequest (RequestAccessory)

- (void)toggleAccessoriesWillStartCallBack;
- (void)toggleAccessoriesWillStopCallBack;
- (void)toggleAccessoriesDidStopCallBack;

@end

@interface YTKBatchRequest (RequestAccessory)

- (void)toggleAccessoriesWillStartCallBack;
- (void)toggleAccessoriesWillStopCallBack;
- (void)toggleAccessoriesDidStopCallBack;

@end

@interface YTKChainRequest (RequestAccessory)

- (void)toggleAccessoriesWillStartCallBack;
- (void)toggleAccessoriesWillStopCallBack;
- (void)toggleAccessoriesDidStopCallBack;

@end

他们都持有requestAccessories:

@property (nonatomic, strong, nullable) NSMutableArray<id<YTKRequestAccessory>> *requestAccessories;

- (void)addAccessory:(id<YTKRequestAccessory>)accessory;

其实个人认为这里的requestAccessories权限应该限制为readonly比较合适,只是一个小建议。

触发时机,这里以YTKBaseRequest为例子,嗯,不解释看代码。

- (void)start {
[self toggleAccessoriesWillStartCallBack];
[[YTKNetworkAgent sharedAgent] addRequest:self];
}

- (void)stop {
[self toggleAccessoriesWillStopCallBack];
self.delegate = nil;
[[YTKNetworkAgent sharedAgent] cancelRequest:self];
[self toggleAccessoriesDidStopCallBack];
}

好了 YTKNetWork的解析就到这里,今天周六,祝大家周末愉快!

Contents
  1. 1. 开源代码信息
  2. 2. 源码分析
    1. 2.1. 1. YTKRequest && YTKBaseRequest 的组成
    2. 2.2. 2. 基本请求过程
    3. 2.3. 3. 批量请求
    4. 2.4. 4. 链式请求
    5. 2.5. 5. 缓存机制
    6. 2.6. 6. 断点续传
    7. 2.7. 7. Filter
    8. 2.8. 8. 插件机制