作者:J_Knight_
链接:https://www.jianshu.com/p/89dd444399ce
來源:简书
一.概述
基于 AFNetworking 进行再一次封装,以及缓存处理。采用了离散式替代传统 AFNetworking 的集约式编程。通过给每一个请求创建一个对象的高度定制性,使 url 以及响应方式等内容不暴露出来,以增加了一些代码工作量为代价,更好的做到业务层与网络层的分离。
二.架构
说明:
- YTKNetwork 框架将每一个请求实例化,YTKBaseRequest 是所有请求类的基类,YTKRequest 是它的子类。所以如果我们想要发送一个请求,则需要创建并实例化一个继承于YTKRequest的自定义的请求类(CustomRequest)并发送请求。
- YTKNetworkAgent 是一个单例,负责管理所有的请求类(例如 CustomRequest )。当 CustomRequest 发送请求以后,会把自己放在 YTKNetworkAgent 持有的一个字典里,让其管理自己。
- 我们说 YTKNetwork 封装了 AFNetworking ,实际上是 YTKNetworkAgent 封装了 AFNetworking ,由它负责 AFNetworking 请求的发送和 AFNetworking 的回调处理。所以如果我们想更换一个第三方网络请求库,就可以在这里更换一下。而 YTKRequest 更多的是只是负责缓存的处理。
三.设计模式
采用命令模式(Command Pattern)设计模式。
命令模式的简单介绍:
- Client:创建具体命令
- Invoker:命令调用者
- Receiver:命令接受者
- ConcreteCommand:具体命令
- Command:抽象命令
在 YTKNetwork 中的对应应用如下表:
命令模式的本质是对命令的封装,将发出命令的责任和执行命令的责任分割开。
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
四.YTKNetwork 各类介绍
类名 | 职责 |
---|---|
YTKBaseRequest | 所有请求类的基类。持有 NSURLSessionTask 实例,responseData,responseObject,error 等重要数据,提供一些需要子类实现的与网络请求相关的方法,处理回调的代理和 block,命令 YTKNetworkAgent 发起网络请求。 |
YTKRequest | YTKBaseRequest 的子类。负责缓存的处理:请求前查询缓存;请求后写入缓存。 |
YTKNetworkConfig | 被 YTKRequest 和 YTKNetworkAgent 访问。负责所有请求的全局配置,例如 baseUrl 和 CDNUrl 等等。 |
YTKNetworkPrivate | 提供 JSON 验证,appVersion 等辅助性的方法;给 YTKBaseRequest 增加一些分类。 |
YTKNetworkAgent | 真正发起请求的类。负责发起请求,结束请求,并持有一个字典来存储正在执行的请求。 |
YTKBatchRequest | 负责管理多个 YTKBatchRequest 实例,持有一个数组来保存 YTKBatchRequest 。支持添加和删除 YTKBatchRequest 实例。 |
YTKChainRequest | 可以发起链式请求,持有一个数组来保存所有的请求类。当某个请求结束后才能发起下一个请求,如果其中有一个请求返回失败,则认定本请求链失败。 |
YTKChainRequestAgent | 负责管理多个 YTKChainRequestAgent 实例,持有一个数组来保存 YTKChainRequest。支持添加和删除 YTKChainRequest 实例。 |
五.基本使用
单个请求配置
官方的教程建议我们将请求的全局配置是在 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;
}
//需要和baseUrl拼接的地址
- (NSString *)requestUrl {
// “ http://www.yuantiku.com ” 在 YTKNetworkConfig 中设置,这里只填除去域名剩余的网址信息
return @"/iphone/register";
}
//请求方法,某人是GET
- (YTKRequestMethod)requestMethod {
return YTKRequestMethodPOST;
}
//请求体
- (id)requestArgument {
return @{
@"username": _username,
@"password": _password
};
}
@end
单个请求的发起
还是刚才的注册 API,在实例化以后,直接调用startWithCompletionBlockWithSuccess:failure
方法(或start方法)就可以发起它:
RegisterApi *api = [[RegisterApi alloc] initWithUsername:username password:password];
[api startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
// 你可以直接在这里使用 self
NSLog(@"succeed");
} failure:^(YTKBaseRequest *request) {
// 你可以直接在这里使用 self
NSLog(@"failed");
}];
六.源码解析
一.封装请求,调用 start 方法
startWithCompletionBlockWithSuccess:failure
来看一下YTKNetwork做了什么:
//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;
}
当保存完成功和失败的 bloc k以后,调用 start 方法,于是来到了 YTKRequest 类(注意,虽然 YTKBaseRequest 也实现了 start 方法,但是由于 YTKRequest 类是它的子类并也实现了 start 方法,所以这里最先走的是 YTKRequest 类的 start 方法):
- (void)start {
//如果忽略缓存
//ignoreCache属性是用户手动设置的,如果用户强制忽略缓存,则无论是否缓存是否存在,直接发送请求。
if (self.ignoreCache) {
//调用无缓存请求方法
[self startWithoutCache];
return;
}
//如果存在下载未完成的文件(不缓存下载请求)
//resumableDownloadPath是断点下载路径,如果该路径不为空,说明有未完成的下载任务,则直接发送请求继续下载。
if (self.resumableDownloadPath) {
//调用无缓存请求方法
[self startWithoutCache];
return;
}
//获取缓存失败
//loadCacheWithError:方法验证了加载缓存是否成功的方法(返回值为YES,说明可以加载缓存;反之亦然
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];
//请求成功的block
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
//手动把成功和失败block都置为nil,避免循环引用
[strongSelf clearCompletionBlock];
});
}
二.判断有缓存情况还是无缓存情况
loadCacheWithError
验证加载缓存是否成功的方法(返回值为YES,说明可以加载缓存;反之亦然),看一下具体实现:
- (BOOL)loadCacheWithError:(NSError * _Nullable __autoreleasing *)error {
//缓存时间小于0,缓存不可用,返回NO(缓存时间默认为-1,需要用户手动设置,单位是秒)
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;
}
//检查缓存是否可用,如果不可用,返回NO
if (![self validateCacheWithError:error]) {
return NO;
}
//检查缓存是否可以加载,如果不能加载,返回NO
if (![self loadCacheData]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheData userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache data"}];
}
return NO;
}
//其他情况表示能够加载缓存
return YES;
}
loadCacheMetadata
我们来看一下上面关于缓存的元数据的获取方法。
- (BOOL)loadCacheMetadata {
//获取缓存元数据路径
NSString *path = [self cacheMetadataFilePath];
NSFileManager * fileManager = [NSFileManager defaultManager];
//使用try catch捕获异常避免崩溃
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;
}
其中 [self cacheMetadataFilePath]
缓存元数据路径获取。
缓存元数据路径由根路径加缓存元数据子路径构成。
- 根路径:默认为 library 路径拼接 "LazyRequestCache" 。如果遵循 YTKCacheDirPathFilterProtocol 协议实现
- (NSString *)filterCacheDirPath:(NSString *)originPath withRequest:(YTKBaseRequest *)request;
方法可更改跟路径。根据路径查询目录,如果目录不存在则创建。 - 缓存元数据子路径: 获取
requestMethod
、baseUrl
、requestUrl
、argument
组成requestInfo
,加密后形成缓存文件名。再加上.metadata
形成缓存元数据子路径。
cacheMetadata(YTKCacheMetadata)
它描述的是缓存的版本号,敏感信息,创建时间,app版本等信息,并支持序列化处理,可以保存在磁盘里。
因此,loadCacheMetadata
方法的目的是将之前被序列化保存的缓存元数据信息反序列化,赋给自身的cacheMetadata
属性上。
//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;
@property (nonatomic, strong) NSString *appVersionString;
@end
validateCacheWithError
逐一验证元数据里的各项信息是否符合要求。
- (BOOL)validateCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// 是否大于过期时间
NSDate *creationDate = self.cacheMetadata.creationDate;
NSTimeInterval duration = -[creationDate timeIntervalSinceNow];
//[self cacheTimeInSeconds] 设定缓存有效时间
if (duration < 0 || duration > [self cacheTimeInSeconds]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorExpired userInfo:@{ NSLocalizedDescriptionKey:@"Cache expired"}];
}
return NO;
}
// 缓存的版本号是否符合
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;
}
// 敏感信息是否符合 (元数据中的敏感信息 与 请求中设定的敏感信息 比对)
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的版本是否符合
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
- (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
当确认缓存可以成功取出后,手动设置 dataFromCache 属性为 YES,说明当前的请求结果是来自于缓存,而没有通过网络请求。
requestCompletePreprocessor
- (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]];
}
}
- (void)saveResponseDataToCacheFile:(NSData *)data {
// 设置缓存时间大于0 且 此数据之前不是缓存数据
if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {
// 数据额非空
if (data != nil) {
@try {
// 总是覆盖旧数据
[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);
}
}
}
}
requestCompleteFilter
- (void)requestCompleteFilter {
}
可以看到此方法内容为空。用户可以在这里做一些回调前的处理。
三.缓存情况下
//到这里一定是有缓存
_dataFromCache = YES;
//开启主线程异步方法
dispatch_async(dispatch_get_main_queue(), ^{
//缓存处理
[self requestCompletePreprocessor];
//用户可以在这里进行真正的回调前操作
[self requestCompleteFilter];
//执行回调
YTKRequest *strongSelf = self;
//请求完成后的代理回调
[strongSelf.delegate requestFinished:strongSelf];
//请求成功的block
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
//手动把成功和失败block都置为nil,避免循环引用
[strongSelf clearCompletionBlock];
});
在有缓存的情况下,直接利用缓存执行代理和 block 的回调
四.无缓存情况下
执行无缓存请求。
[self startWithoutCache];
startWithoutCache
- (void)startWithoutCache {
// 清除本类中全局变量
[self clearCacheVariables];
//调用父类的 start 方法
[super start];
}
- (void)clearCacheVariables {
_cacheData = nil;
_cacheXML = nil;
_cacheJSON = nil;
_cacheString = nil;
_cacheMetadata = nil;
_dataFromCache = NO;
}
YTKBaseRequest start 方法
- (void)start {
// 告诉Accessories即将回调了(其实是即将发起请求)
[self toggleAccessoriesWillStartCallBack];
// Agent添加请求
[[YTKNetworkAgent sharedAgent] addRequest:self];
}
- (void)toggleAccessoriesWillStartCallBack {
// 遵循YTKRequestAccessory代理的类执行start方法
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStart:)]) {
[accessory requestWillStart:self];
}
}
}
addRequest
- (void)addRequest:(YTKBaseRequest *)request {
// 断言request不为空
NSParameterAssert(request != nil);
// 定义error默认为空
NSError * __autoreleasing requestSerializationError = nil;
// 获取自定义request,默认为空 如果自定义子类中中实现buildCustomUrlRequest方法则获取方法中的request
NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
// 判断自定义request是否存在
if (customUrlRequest) {
__block NSURLSessionDataTask *dataTask = nil;
// 调用afn中网络请求方法
dataTask = [_manager dataTaskWithRequest:customUrlRequest completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
// 使用handleRequestResult处理afn返回结果
[self handleRequestResult:dataTask responseObject:responseObject error:error];
}];
request.requestTask = dataTask;
} else {
// 如果自定义request不存在,使用传入的request(即baseRequest类型的具体request)
request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
}
// 如果请求序列化错误
if (requestSerializationError) {
// 处理错误
[self requestDidFailWithRequest:request error:requestSerializationError];
return;
}
// 断言
NSAssert(request.requestTask != nil, @"requestTask should not be nil");
// ios8 以后可以设定请求优先级
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;
}
}
YTKLog(@"Add request: %@", NSStringFromClass([request class]));
//请求备份
[self addRequestToRecord:request];
// 开始请求
[request.requestTask resume];
}
此方法主要分三个部分:
- 第一部分是获取当前请求对应的task并赋给request的requestTask属性
- 第二部分是把request放入专门用来保存请求的字典中
- 第三部分是启动task
sessionTaskForRequest: error :
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
// 获取请求方法类型
YTKRequestMethod method = [request requestMethod];
// 获取url
NSString *url = [self buildRequestUrl:request];
// 获取请求参数
id param = request.requestArgument;
AFConstructingBlock constructingBlock = [request constructingBodyBlock];
// 获取request serializer
AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];
// 根据不同请求方法类型返回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:
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];
}
}
五. NSURLSessionTask 参数配置
URL处理
//YTKNetworkAgent.m
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
...
NSString *url = [self buildRequestUrl:request];
...
}
- (NSString *)buildRequestUrl:(YTKBaseRequest *)request {
// 断言
NSParameterAssert(request != nil);
//获取子类中自定义requesturl
NSString *detailUrl = [request requestUrl];
NSURL *temp = [NSURL URLWithString:detailUrl];
// 存在host和scheme的url立即返回正确
if (temp && temp.host && temp.scheme) {
return detailUrl;
}
// 如果遵循YTKUrlFilterProtocol 重写filterUrl方法 过滤url
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];
}
}
// 如果末尾没有/,则在末尾添加一个/
NSURL *url = [NSURL URLWithString:baseUrl];
if (baseUrl.length > 0 && ![baseUrl hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
// 拼接baseUrl和detailUrl并返回完整url
return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
}
requestSerializerForRequest
- (AFHTTPRequestSerializer *)requestSerializerForRequest:(YTKBaseRequest *)request {
AFHTTPRequestSerializer *requestSerializer = nil;
// 判断请求的序列化类型是HTTP还是JSON (可通过requestSerializerType手动设定)
if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
// 创建HTTP类型
requestSerializer = [AFHTTPRequestSerializer serializer];
} else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
// 创建JSON类型
requestSerializer = [AFJSONRequestSerializer serializer];
}
// 超时时间 默认60 可手动设置
requestSerializer.timeoutInterval = [request requestTimeoutInterval];
// 是否允许使用蜂窝移动数据(4g)默认可以 可手动设置
requestSerializer.allowsCellularAccess = [request allowsCellularAccess];
// 如果当前请求需要服务器账号和密码
NSArray<NSString *> *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
if (authorizationHeaderFieldArray != nil) {
[requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFieldArray.firstObject
password:authorizationHeaderFieldArray.lastObject];
}
// 如果当前请求需要自定义 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;
}
六.开始请求
获取 task
- (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使用AFN中的AFHTTPRequestSerializer创建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;
}
启动task
[request.requestTask resume];
七.处理回调
handleRequestResult
响应的统一处理内部操作
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
// 根据task获取request
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;
// 获取request对应的response
request.responseObject = responseObject;
if ([request.responseObject isKindOfClass:[NSData class]]) {
// 获取 responseData
request.responseData = responseObject;
// 获取responseString
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;
}
}
// 判断是否有错误,将错误对象赋值给requestError,改变succeed的布尔值。目的是根据succeed的值来判断到底是进行成功的回调还是失败的回调
if (error) {
// 如果该方法传入的error不为nil
succeed = NO;
requestError = error;
} else if (serializationError) {
// 如果序列化失败了
succeed = NO;
requestError = serializationError;
} else {
// 即使没有error而且序列化通过,也要验证request是否有效
succeed = [self validateResult:request error:&validationError];
requestError = validationError;
}
// 根据succeed的布尔值来调用相应的处理
if (succeed) {
// 请求成功的处理
[self requestDidSucceedWithRequest:request];
} else {
// 请求失败的处理
[self requestDidFailWithRequest:request error:requestError];
}
// 回调完成的处理
dispatch_async(dispatch_get_main_queue(), ^{
// 在字典里移除当前request
[self removeRequestFromRecord:request];
// 清除所有block
[request clearCompletionBlock];
});
}
简单讲解一下上面的代码:
- 前面以 task 的 identifier 为 key,将 request 存入字典,这里根据 task 取出 request。
- 然后将获得的responseObject进行处理,将处理后获得的responseObject,responseData和responseString赋值给当前的请求实例request。
- 再根据这些值的获取情况来判断最终回调的成败(改变succeed的值)。
- 最后根据succeed的值来进行成功和失败的回调。
validateResult
验证返回的json数据是否有效
- (BOOL)validateResult:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
// 判断statusCode是否在200~299之间,如果是则返回状态有效
BOOL result = [request statusCodeValidator];
if (!result) {
// 如果状态码无效,传入error地址存在
if (error) {
// 创建NSError对象赋值给error响应地址
*error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidStatusCode userInfo:@{NSLocalizedDescriptionKey:@"Invalid status code"}];
}
// 返回result
return result;
}
// 状态码有效的情况下判断json是否有效
// 去返回json
id json = [request responseJSONObject];
// 取设定的json数据格式,可手动设置
id validator = [request jsonValidator];
// 如果同时存在
if (json && validator) {
// 判断json是否符合设定的jsonValidator
result = [YTKNetworkUtils validateJSON:json withValidator:validator];
if (!result) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidJSONFormat userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON format"}];
}
return result;
}
}
return YES;
}
在这里,首先,用statusCodeValidator方法判断响应的code是否在正确的范围,然后再判断json的有效性。
validateJSON
判断目标字典对象是否满足于设置字典模型的key一致时,value类型与自定义类型格式相同
+ (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遍历validator的key数组,直到为空
while ((key = [enumerator nextObject]) != nil) {
// 根据key取dict字典value,和validator字典中定义类型
id value = dict[key];
id format = validator[key];
// 如果vlue仍为字典或者数组,递归
if ([value isKindOfClass:[NSDictionary class]]
|| [value isKindOfClass:[NSArray class]]) {
result = [self validateJSON:value withValidator:format];
if (!result) {
break;
}
} else {
// 如果value不为定义类型 且 value不为空类型
if ([value isKindOfClass:format] == NO &&
[value isKindOfClass:[NSNull class]] == NO) {
// 结果为NO
result = NO;
// 有一个结果为NO则全部为NO, 不需要再循环,跳出
break;
}
}
}
// 返回结果
return result;
// 如果json和jsonValidator同为数组类型
} 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;
// 类型直接相同返回yes
} else if ([json isKindOfClass:jsonValidator]) {
return YES;
// 类型不同返回no
} else {
return NO;
}
}
requestDidSucceedWithRequest
请求成功的处理。
- (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];
});
}
requestDidFailWithRequest
请求失败处理。
- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error {
// 赋值error
request.error = error;
YTKLog(@"Request %@ failed, status code = %ld, error = %@",
NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription);
// 储存未完成的下载数据,存入resumableDownloadPath,等待断点续传
NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
if (incompleteDownloadData) {
[incompleteDownloadData writeToURL:[self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] atomically:YES];
}
// 下载失败清除文件目录中的文件并清空响应
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];
}
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里面。而如果下载任务失败,则将其对应的在本地保存的路径上的文件清空。
八.取消请求
两个取消方法:
//YTKNetworkAgent.h
/// 取消某个request
- (void)cancelRequest:(YTKBaseRequest *)request;
/// 取消所有添加的request
- (void)cancelAllRequests;
cancelRequest
取消某个request。
- (void)cancelRequest:(YTKBaseRequest *)request {
// 断言request不为空
NSParameterAssert(request != nil);
// 如果下载过程中取消
if (request.resumableDownloadPath) {
NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
// 写入resumableDownloadPath等待断点续传。方法内部会调用cancel方法
[requestTask cancelByProducingResumeData:^(NSData *resumeData) {
NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
[resumeData writeToURL:localUrl atomically:YES];
}];
} else {
// 获取request的task,并取消
[request.requestTask cancel];
}
// 从字典里移除当前request
[self removeRequestFromRecord:request];
// 清理所有block
[request clearCompletionBlock];
}
- (void)removeRequestFromRecord:(YTKBaseRequest *)request {
// 加锁
Lock();
// 移除字典中这组数据
[_requestsRecord removeObjectForKey:@(request.requestTask.taskIdentifier)];
YTKLog(@"Request queue size = %zd", [_requestsRecord count]);
Unlock();
}
cancelAllRequests
取消所有request。
- (void)cancelAllRequests {
// 加锁。多个线程对同一个字典进行操作的,都需要加锁
Lock();
NSArray *allKeys = [_requestsRecord allKeys];
Unlock();
if (allKeys && allKeys.count > 0) {
// copy
NSArray *copiedKeys = [allKeys copy];
for (NSNumber *key in copiedKeys) {
Lock();
YTKBaseRequest *request = _requestsRecord[key];
Unlock();
// We are using non-recursive lock.
// Do not lock `stop`, otherwise deadlock may occur.
// stop每个请求
[request stop];
}
}
}
stop
- (void)stop {
// 告诉Accessories将要停止回调了
[self toggleAccessoriesWillStopCallBack];
// 清空代理
self.delegate = nil;
// 调用agent的取消某个request的方法
[[YTKNetworkAgent sharedAgent] cancelRequest:self];
// 告诉Accessories已经停止回调完成了
[self toggleAccessoriesDidStopCallBack];
}
九.批量请求
初始化
- (instancetype)initWithRequestArray:(NSArray<YTKRequest *> *)requestArray {
self = [super init];
if (self) {
// 保存为属性,使用copy即使requestArray改变_requestArray也不会变
_requestArray = [requestArray copy];
//批量请求完成的数量初始化为0
_finishedCount = 0;
//类型检查,所有元素都必须为YTKRequest或的它的子类,否则强制初始化失败
for (YTKRequest * req in _requestArray) {
if (![req isKindOfClass:[YTKRequest class]]) {
YTKLog(@"Error, request item must be YTKRequest instance.");
return nil;
}
}
}
return self;
}
初始化以后,我们就可以调用start方法来发起当前YTKBatchRequest实例所管理的所有请求了。
- (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];
}
}
requestFinished
单个请求完成,如果全部完成则批量请求完成。
- (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];
}
}
requestFailed
单个请求失败即批量请求失败,将失败的请求保存到_failedRequest中
- (void)requestFailed:(YTKRequest *)request {
// 失败请求保存在_failedRequest中
_failedRequest = request;
// 调用即将结束的代理
[self toggleAccessoriesWillStopCallBack];
// 停止batch里所有的请求
for (YTKRequest *req in _requestArray) {
[req stop];
}
// 调用批量请求失败的代理
if ([_delegate respondsToSelector:@selector(batchRequestFailed:)]) {
[_delegate batchRequestFailed:self];
}
// 调用请求失败的block
if (_failureCompletionBlock) {
_failureCompletionBlock(self);
}
// 清空成功和失败的block
[self clearCompletionBlock];
// 调用请求结束的代理
[self toggleAccessoriesDidStopCallBack];
// 从YTKBatchRequestAgent里移除当前的batch
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
十.链式请求
和批量请求类似,处理链式请求的类是YTKChainRequest,并且用YTKChainRequestAgent单例来管理YTKChainRequest的实例
init
初始化方法。
- (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;
}
addRequest
添加request的接口。
- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback {
// 添加当前请求进入请求数组
[_requestArray addObject:request];
// 传入回调Block时
if (callback != nil) {
// 回调block存入回调数组
[_requestCallbackArray addObject:callback];
} else {
// 之前之所以创建一个空的block回调是为了在用户不传入回调时对应传入创建的空回调,保持添加的请求和回调个数一致一一对应。
[_requestCallbackArray addObject:_emptyCallback];
}
}
start
请求的发起。
- (void)start {
// 如果第1个请求已经结束,就不再重复start了。nextRequestIndex为0,请求重头开始。
if (_nextRequestIndex > 0) {
YTKLog(@"Error! Chain request has already started.");
return;
}
// //如果请求队列数组里面还有request,则取出并start
if ([_requestArray count] > 0) {
// 将要开始回调
[self toggleAccessoriesWillStartCallBack];
// 开始下一个请求
[self startNextRequest];
// 在YTKChainRequestAgent请求数组中加入当前链式请求(即可以有多个链式请求同时存在)
[[YTKChainRequestAgent sharedAgent] addChainRequest:self];
} else {
YTKLog(@"Error! Chain request array is empty.");
}
}
- (BOOL)startNextRequest {
// 判断不超出数组
if (_nextRequestIndex < [_requestArray count]) {
// 取出request
YTKBaseRequest *request = _requestArray[_nextRequestIndex];
// index+1
_nextRequestIndex++;
// 指定代理
request.delegate = self;
// 清除block
[request clearCompletionBlock];
// 开始请求
[request start];
return YES;
} else {
return NO;
}
}
所以和批量请求不同的是,链式请求的请求队列是可以变动的,用户可以无限制地添加请求。只要请求队列里面有请求存在,则YTKChainRequest就会继续发送它们。
成功回调
- (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列表中移除当前链式请求
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
}
// 链式请求结束
[self toggleAccessoriesDidStopCallBack];
}
}
失败回调
- (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];
}
stop
- (void)stop {
//首先调用即将停止的callback
[self toggleAccessoriesWillStopCallBack];
//然后stop当前的请求,再清空chain里所有的请求和回掉block
[self clearRequest];
//在YTKChainRequestAgent里移除当前的chain
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
//最后调用已经结束的callback
[self toggleAccessoriesDidStopCallBack];
}
- (void)clearRequest {
//获取当前请求的index
NSUInteger currentRequestIndex = _nextRequestIndex - 1;
if (currentRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[currentRequestIndex];
// 停止当前请求
[request stop];
}
//清空请求数组
[_requestArray removeAllObjects];
//请求回调数组
[_requestCallbackArray removeAllObjects];
}