我们经常把数据请求和数据处理放到Controller中,导致对AFNetwork的依赖过高,实际上AFNetwork只是一个网络支撑平台,我们需要根据业务需求(加密request、缓存、批量请求等)对它进行二次封装,YTKNetwork做了比较好的封装。
YTKNetwork 的基本思想
YTKNetwork 的基本的思想是把每一个网络请求封装成对象。所以使用 YTKNetwork,你的每一个请求都需要继承 YTKRequest 类,通过覆盖父类的一些方法来构造指定的网络请求。
YTKNetwork架构
熟悉架构以后能更充分的理解其设计思想和原理
#import "YTKRequest.h"
#import "YTKBaseRequest.h"
#import "YTKNetworkAgent.h"
#import "YTKBatchRequest.h"
#import "YTKBatchRequestAgent.h"
#import "YTKChainRequest.h"
#import "YTKChainRequestAgent.h"
#import "YTKNetworkConfig.h"
YTKNetwork采用的设计模式是命令模式,这个模式的特点是将命令包裹成对象,传递给调用对象,调用对象再去寻找合适的能够执行命令的对象,把命令传给它来执行任务。
- 命令模式的目的:
在软件系统中,行为请求和行为实现者往往是紧耦合关系,如果需要对行为进行记录、终止、事务等操作,紧耦合就显得不是很合理。 - 命令模式的优缺点
优点:降低了耦合度,新的命令可以很容易添加到系统中。
缺点:会导致系统产生很多命令类。
命令模式类图
在YTKNetwork中各个类分属如下:
- invoker:YTKNetworkAgent,命令调用者。
- Command:YTKBaseRequest,抽象命令,命令都在这个Command里面声明。
- ConcreteCommand:YTKRequest,命令实现类。
- Receiver:AFNetwork,命令执行者。
- Client:ViewController/ViewModel,发起命令者。
使用
正如我们上面所说,YTKNetwork是命令模式,需要把请求request
当做一个具体命令,所以需要创建一个继承于YTKRequest
的命令类,
#import "YTKRequest.h"
@interface RegisterApi : YTKRequest
- (id)initWithUsername:(NSString *)username password:(NSString *)password;
- (NSString *)userId;
@end
@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 {
return @"/iphone/register";
}
- (YTKRequestMethod)requestMethod {
return YTKRequestMethodPOST;
}
- (id)requestArgument {
return @{
@"username": _username,
@"password": _password
};
}
@end
上面代码就是实现的具体命令类
-
requestUrl
:请求需要拼接在baseUrl后面的接口url -
requestMethod
:请求类型YTKRequestMethodPOST/YTKRequestMethodGET
等 -
requestArgument
:请求参数
这样就构成了一个基本的命令类。
源码解析
上面我们创建了一个具体命令类RegisterApi
,官方给出的Demo是如下调用的
TestApi *testApi = [[TestApi alloc] init];
[testApi startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest * _Nonnull request) {
NSLog(@"成功---- %@",request.responseObject);
} failure:^(__kindof YTKBaseRequest * _Nonnull request) {
NSLog(@"失败");
}];
需要调用startWithCompletionBlockWithSuccess
来开始具体的请求,进入YTKBaseRequest
看源码,这里可以分成两块儿,从缓存读取数据和从网络请求数据。
#import "YTKBaseRequest.h"
- (void)startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
//持有成功和失败的 block
[self setCompletionBlockWithSuccess:success failure:failure];
[self start];
}
调用start
方法开始请求,这里调用的start方法是子类YTKRequest
中的start重载的方法。
- (void)start {
//是否忽略缓存
if (self.ignoreCache) {
[self startWithoutCache];
return;
}
//是否有未下载完的文件
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];
//防止self在外部被释放,效果等同于__strong typeof(self) strongSelf = self;
YTKRequest *strongSelf = self;
//成功的代理回调
[strongSelf.delegate requestFinished:strongSelf];
//成功的 block 回调
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
//清空当前持有的 block,避免循环引用,self-/->block-->self
[strongSelf clearCompletionBlock];
});
}
这段代码主要是判断是否有缓存,有缓存就读取,没缓存就startWithoutCache
直接请求。先看有缓存的情况下,怎么取的缓存,请看下面的从缓存读取数据部分。
从缓存读取数据
通过上面代码中的判断条件我们可以知道,[self loadCacheWithError:nil]
会试着读取缓存,成功则接着走下面的方法从处理缓存,否则需要做请求。
- (BOOL)loadCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// Make sure cache time in valid.
//需要用户手动设置缓存时间,默认是-1
if ([self cacheTimeInSeconds] < 0) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheTime userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache time"}];
}
return NO;
}
// Try load metadata.
//如果有缓存元数据,则读取缓存元数据,没有则return NO;(告诉上层调用者没有缓存,老实请求吧)
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.读取缓存,此时这个操作就把c缓存读取下来了
if (![self loadCacheData]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheData userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache data"}];
}
return NO;
}
return YES;
}
1、[self cacheTimeInSeconds]
,判断是否给这个请求命令设置缓存时间了,没设置的话代表这个请求不需要缓存。
2、[self loadCacheMetadata]
,如果有缓存元数据,则读取缓存元数据,没有则return NO;(告诉上层调用者没有缓存,老实请求吧),这个元数据在下面会解释。
3、[self validateCacheWithError:error]
,判断是否是有效的缓存数据。
4、[self loadCacheData]
,读取缓存,此时这个操作就把c缓存读取下来了。
下面从以2、3、4分为三部分来看一下具体怎么读的缓存
Part1 读取缓存元数据
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;
}
cacheMetadataFilePath
是缓存元数据的路径,进里面简单看看存在哪?
//创建名为LazyRequestCache的目录存放缓存
- (NSString *)cacheBasePath {
NSString *pathOfLibrary = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [pathOfLibrary stringByAppendingPathComponent:@"LazyRequestCache"];
// Filter cache base path
NSArray> *filters = [[YTKNetworkConfig sharedConfig] cacheDirPathFilters];
if (filters.count > 0) {
for (id f in filters) {
path = [f filterCacheDirPath:path withRequest:self];
}
}
[self createDirectoryIfNeeded:path];
return path;
}
//缓存的名字
- (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;
}
我们通过代码可以知道,缓存元数据是以文件序列化形式存储的,什么是缓存元数据?
@interface YTKCacheMetadata : NSObject
@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;//app版本号
@end
目前我们知道的缓存元数据就是这个。
Part2判断是否是有效的缓存元数据
validateCacheWithError
是通过cache的过期时间、版本号、敏感数据以及app的版本是否符合来判断缓存数据是否有效的。
- (BOOL)validateCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// Date
NSDate *creationDate = self.cacheMetadata.creationDate;
NSTimeInterval duration = -[creationDate timeIntervalSinceNow];
//判断cache过期了没有。
if (duration < 0 || duration > [self cacheTimeInSeconds]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorExpired userInfo:@{ NSLocalizedDescriptionKey:@"Cache expired"}];
}
return NO;
}
// Version,判断cache版本号是否正确
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;
}
Part3 读取缓存数据
- (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;
}
- (NSString *)cacheFilePath {
NSString *cacheFileName = [self cacheFileName];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheFileName];
return path;
}
从代码中我们可以得知,缓存数据是以二进制的形式存储在LazyRequestCache
文件夹下的,缓存元数据也是存储在这个文件夹下。
@interface YTKRequest()
@property (nonatomic, strong) NSData *cacheData;
@property (nonatomic, strong) NSString *cacheString;
@property (nonatomic, strong) id cacheJSON;
@property (nonatomic, strong) NSXMLParser *cacheXML;
@property (nonatomic, strong) YTKCacheMetadata *cacheMetadata;
@property (nonatomic, assign) BOOL dataFromCache;
@end
缓存数据读取成功后,上面的对应属性就会有缓存值,并开始对缓存的处理,dataFromCache
变为YES,即在判断是否有缓存时,如果有就把缓存取出来了,如下面代码所示,
- (void)start {
···
dispatch_async(dispatch_get_main_queue(), ^{
//处理缓存,保存缓存
[self requestCompletePreprocessor];
//做回调数据成功后的处理,需要自行实现
[self requestCompleteFilter];
//防止self在外部被释放,效果等同于__strong typeof(self) strongSelf = self;
YTKRequest *strongSelf = self;
//成功的代理回调
[strongSelf.delegate requestFinished:strongSelf];
//成功的 block 回调
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
//清空当前持有的 block,避免循环引用,self-/->block-->self
[strongSelf clearCompletionBlock];
});
}
- (void)requestCompletePreprocessor {
[super requestCompletePreprocessor];
//数据 + 元数据(请求数据的描述)
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 {
// 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
方法做的是更新缓存的操作,从saveResponseDataToCacheFile
方法中可以看出,更新的是缓存数据和缓存元数据两个缓存,当然当前在start
请求时,是从缓存中拿到的数据,所以不需要进行更新。 -
requestCompleteFilter
做回调数据成功后的处理,需要自行在具体命令类中实现 - 请求回调部分:可以通过
delegate
代理回调,也可以通过block
回调。
从缓存读取数据的总结
- YTKNetwork的缓存数据分为缓存元数据和缓存数据
NSData
,缓存元数据是对缓存数据的描述。 - 读取缓存进行了四重验证:
1、是否设置了缓存时间
2、获取缓存元数据
3、判断缓存元数据是否有效
4、获取缓存数据
只要有一步错误就证明没有缓存,需要重新请求获取数据。
从网络请求数据
如果没能从缓存中获取到数据,则直接进行网络请求
- (void)startWithoutCache {
[self clearCacheVariables];
//
[super start];
}
先清空缓存变量
- (void)clearCacheVariables {
_cacheData = nil;
_cacheXML = nil;
_cacheJSON = nil;
_cacheString = nil;
_cacheMetadata = nil;
_dataFromCache = NO;
}
调用父类YTKBaseRequest
的start
方法。
#import "YTKBaseRequest.h"
- (void)start {
//响应代理即将开始请求,requestWillStart
[self toggleAccessoriesWillStartCallBack];
[[YTKNetworkAgent sharedAgent] addRequest:self];
}
-
toggleAccessoriesWillStartCallBack
:该方法定义在YTKNetworkPrivate
中,这个类中包含了一些YTKBaseRequest
的分类,这个方法就在RequestAccessory
分类中,主要用于通知Request
的状态。 -
[[YTKNetworkAgent sharedAgent] addRequest:self];
:调用YTKNetworkAgent
的addRequest
开始真正的请求。
那么这里就分两步,Part1是请求状态的改变通知,Part2是开启Task请求
Part1 请求状态的改变通知
YTKBaseRequest
有个代理YTKRequestAccessory
,用于通知用户请求的状态的。用户只要遵守YTKRequestAccessory
这个代理协议,就可以通过下面的代理方法监听到request状态的改变
@protocol YTKRequestAccessory
@optional
- (void)requestWillStart:(id)request;
- (void)requestWillStop:(id)request;
- (void)requestDidStop:(id)request;
@end
toggleAccessories
系列的方法定义在YTKNetworkPrivate
中的分类里
#import "YTKNetworkPrivate.h"
@interface YTKBaseRequest (RequestAccessory)
- (void)toggleAccessoriesWillStartCallBack;
- (void)toggleAccessoriesWillStopCallBack;
- (void)toggleAccessoriesDidStopCallBack;
@end
@implementation YTKBaseRequest (RequestAccessory)
- (void)toggleAccessoriesWillStartCallBack {
for (id accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStart:)]) {
[accessory requestWillStart:self];
}
}
}
- (void)toggleAccessoriesWillStopCallBack {
for (id accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStop:)]) {
[accessory requestWillStop:self];
}
}
}
- (void)toggleAccessoriesDidStopCallBack {
for (id accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestDidStop:)]) {
[accessory requestDidStop:self];
}
}
}
@end
Part2 开启Task请求
- (void)addRequest:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
NSError * __autoreleasing requestSerializationError = nil;
//1、获取NSURLSessionTask网络请求任务
NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
if (customUrlRequest) {
//1.1用户是否有自定义请求,如果有自定义请求需要在具体命令类中实现这个方法
__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 {
//1.2如果没有自定义请求,则设置NSURLSessionTask
request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
}
if (requestSerializationError) {
[self requestDidFailWithRequest:request error:requestSerializationError];
return;
}
NSAssert(request.requestTask != nil, @"requestTask should not be nil");
// Set request task priority
// !!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]));
//保存request到字典中,taskIdentifier为key,request为value
[self addRequestToRecord:request];
//开启请求任务
[request.requestTask resume];
}
addRequest方法主要做的事情就是开启Task网络请求任务,它需要经过三步:
- 1、获取Task:调用
buildCustomUrlRequest
判断用户是否实现了该方法创建自定义的request请求,如果有则直接通过AFN的dataTaskWithRequest
方法获取Task,否则需要进行一系列设置,再通过AFN的dataTaskWithRequest
获取Task。 - 2、保存request:获得了Task,为了方便以后作取消任务等操作,将Task保存在了字典中,taskIdentifier为key,request为value
- 3、开启Task请求任务:
[request.requestTask resume];
Part2.1 设置Task请求
一个网络请求需要如下参数
1、method
:获取请求类型:默认是Get请求
2、url
:请求地址
3、params
:请求参数
4、constructingBlock
:多表单情况下的formData
5、requestSerializer
:requestSerializer实例
YTKNetwork的操作如下
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
//获取请求类型:默认是Get请求
YTKRequestMethod method = [request requestMethod];
//获取请求url
NSString *url = [self buildRequestUrl:request];
//获取请求参数
id param = request.requestArgument;
//拼接当前的多表单,formData
AFConstructingBlock constructingBlock = [request constructingBodyBlock];
//获取requestSerializer实例
AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];
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];
}
}
我们以POST请求为例,
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(nullable void (^)(id formData))block
error:(NSError * _Nullable __autoreleasing *)error {
NSMutableURLRequest *request = nil;
//判断是否设置了AFConstructingBlock多表单请求
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;
}
这段代码就是开始做请求了,利用AFN的manager做的网络请求,返回NSURLSessionDataTask
给上层,以便控制任务的状态和保存request请求。
并在当前类YTKNetworkAgent
中处理请求的回调handleRequestResult:responseObject:error
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
//加锁防止在获取request时不准确
Lock();
YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
Unlock();
//判断如果request是空的话,表示请求可能被取消或移除了
if (!request) {
return;
}
/** 为什么使用__autoreleasing
http://ihomway.cc/2016/12/28/NSError-autoreleasing-error%E4%B8%AD%E7%9A%84-autoreleasing%E4%BF%AE%E9%A5%B0%E7%AC%A6/
*/
NSError * __autoreleasing serializationError = nil;
NSError * __autoreleasing validationError = nil;
NSError *requestError = nil;
BOOL succeed = NO;
request.responseObject = responseObject;
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];
});
}
这个方法的作用是
1、更新request请求的回调参数responseObject
2、执行请求成功或失败的后续操作
3、移除request,以及成功、失败的block回调,避免循环引用
1、3操作不必多说,就像代码展示的一样,主要是第2步,成功和失败的后续处理
请求成功后,requestDidSucceedWithRequest
的代码如下
- (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];
}
if (request.successCompletionBlock) {
request.successCompletionBlock(request);
}
[request toggleAccessoriesDidStopCallBack];
});
}
-
requestCompletePreprocessor
:我们在一开始讨论个这个方法,主要做的是更新缓存的操作。 -
toggleAccessoriesWillStopCallBack
和toggleAccessoriesDidStopCallBack
:更新request请求状态,遵守YTKRequestAccessory
这个代理协议,就可以通过代理方法监听到request状态的改变。 -
requestCompleteFilter
:请求成功后的操作,需要用户自己在具体命令类中实现。 - 代理和block回调。
请求失败后,requestDidFailWithRequest
的代码如下
- (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.
//如果是下载任务,则保存已经下载完的数据到resumableDownloadPath路径下,以便下次接着下载
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.
// 如果是下载任务,则从url路径中获取responseData和responseString回调,并把responseObject置空
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];
}
//request请求状态改变的通知和代理、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];
});
}
请求失败的处理主要是针对下载任务,
1、如果是下载任务,则保存已经下载完的数据到resumableDownloadPath路径下,以便下次接着下载;从url路径中获取responseData和responseString回调,并把responseObject置空。
2、发送请求的通知和回调。
从网络请求数据的总结
1、整理NSURLSessionTask
请求,针对不同的request请求做处理,并以TaskIdentifier
为key
存储request
,方便日后cancelRequest
等操作。
2、处理回调handleRequestResult:responseObject:error
2.1、更新request请求的回调参数responseObject
2.2、执行请求成功或失败的后续操作
2.3、移除request,以及成功、失败的block回调,避免循环引用