YTKNetWork
是猿题库的团队出的一个基于AFNetWork
的网络请求框架,它对请求结果的缓存和接口版本管理做了进一步封装。
YTKNetWork
提供了请求的基类YTKBaseRequest
,这个类基本没做什么工作,主要是定义公共的属性和方法。接着YTKRequest
继承了YTKBaseRequest
,实现了缓存和接口版本管理的主要逻辑。
基本使用
我们要通过YTKNetWork
发起一个请求,首先要继承YTKRequest
实现一个子类,在里面复写请求方法、地址、参数等方法:
@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
接着就是发起请求:
RegisterApi *registerApi = [[RegisterApi alloc] initWithUsername:@"username" password:@"password"];
[registerApi startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest * _Nonnull request) {
if (request.responseObject) {
}
} failure:^(__kindof YTKBaseRequest * _Nonnull request) {
if (request.error) {
}
}];
除了startWithCompletionBlockWithSuccess
方法,我们也可以直接调用start
方法来发起请求,这种方法需要我们设置registerApi
的delegate
来处理请求成功和失败的结果:
@protocol YTKRequestDelegate <NSObject>
@optional
/// Tell the delegate that the request has finished successfully.
///
/// @param request The corresponding request.
- (void)requestFinished:(__kindof YTKBaseRequest *)request;
/// Tell the delegate that the request has failed.
///
/// @param request The corresponding request.
- (void)requestFailed:(__kindof YTKBaseRequest *)request;
@end
请求流程
先来看看startWithCompletionBlockWithSuccess
里面做了什么:
- (void)startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
[self setCompletionBlockWithSuccess:success failure:failure];
[self start];
}
这个方法是在YTKBaseRequest
里实现的,先是把处理结果的block
保存起来,最后也是调用到了start这个函数。
YTKRequest
复写了start函数:
- (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];
});
}
主要工作:
- 1、先判断是否忽略缓存,若是则直接发起网络请求;
- 2、因为下载的请求是不会缓存的,所以也直接发起网络请求;
- 3、加载缓存的数据,如果失败则发起网络请求;
- 4、标记结果来自缓存,处理请求成功的结果。
从缓存获取数据
来看看从缓存加载数据的代码loadCacheWithError
- (BOOL)loadCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// Make sure cache time in valid.
if ([self cacheTimeInSeconds] < 0) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheTime userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache time"}];
}
return NO;
}
// Try load metadata.
if (![self loadCacheMetadata]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidMetadata userInfo:@{ NSLocalizedDescriptionKey:@"Invalid metadata. Cache may not exist"}];
}
return NO;
}
// Check if cache is still valid.
if (![self validateCacheWithError:error]) {
return NO;
}
// Try load cache.
if (![self loadCacheData]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheData userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache data"}];
}
return NO;
}
return YES;
}
主要任务:
- 1、
[self cacheTimeInSeconds]
返回的是缓存数据过期的时间,如果小于0,那所有的缓存数据都会过期,所以从缓存获取失败; - 2、加载
metadata
,它包含了缓存数据的版本和创建时间等等; - 3、判断缓存数据是否有效;
- 4、来到真正加载缓存数据的方法。
先看看加载metadata的方法:
- (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;
}
metadata的缓存地址
- (NSString *)cacheMetadataFilePath {
NSString *cacheMetadataFileName = [NSString stringWithFormat:@"%@.metadata", [self cacheFileName]];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheMetadataFileName];
return path;
}
获取basepath的方法:
- (NSString *)cacheBasePath {
NSString *pathOfLibrary = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [pathOfLibrary stringByAppendingPathComponent:@"LazyRequestCache"];
// 默认的缓存地址
// ***/Application/823D3A1D-33A6-4625-A03C-5B7FE5F29EB1/Library/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;
}
默认的缓存地址是:***/Application/823D3A1D-33A6-4625-A03C-5B7FE5F29EB1/Library/LazyRequestCache
,如果在YTKNetworkConfig
里面配置了自定义的缓存地址就会选择自定义的缓存地址。
接下来就是创建缓存文件夹:
- (void)createDirectoryIfNeeded:(NSString *)path {
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir;
if (![fileManager fileExistsAtPath:path isDirectory:&isDir]) {
[self createBaseDirectoryAtPath:path];
} else {
if (!isDir) {
NSError *error = nil;
[fileManager removeItemAtPath:path error:&error];
[self createBaseDirectoryAtPath:path];
}
}
}
先判断文件路径是否已经存在,如果不存在或者不是文件夹则都需要去创建:
- (void)createBaseDirectoryAtPath:(NSString *)path {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES
attributes:nil error:&error];
if (error) {
YTKLog(@"create cache directory failed, error = %@", error);
} else {
[YTKNetworkUtils addDoNotBackupAttribute:path];
}
}
创建成功后一个关键代码:[YTKNetworkUtils addDoNotBackupAttribute:path]
,主要是告诉iCloud不要自动备份这个文件夹。
至此我们已经拿到metadata的缓存路径了,如果该文件路径存在,则加载metadate: _cacheMetadata = [NSKeyedUnarchiver unarchiveObjectWithFile:path]
;
来看看metadata的模型:
@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
拿到metadata后就是判断缓存数据是否还可用:
- (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;
}
这里的主要任务是:
- 1、判断缓存时间是否已经过期;
- 2、判断版本是否一致;
- 3、判断敏感数据是否一致,应该是用来自定义的;
- 4、判断APP的版本是否一致,如果不一致也会判定为无效缓存;
判断缓存数据有效后就会开始真正加载缓存的数据:
- (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;
}
看看缓存文件名是如何生成:
- (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;
}
用请求方法、地址、参数拼接字符串再通过md5加密而成。
拿到缓存数据后就开始按照解析类型对数据进行解析。
以上是通过缓存取数据的流程,从缓存取失败后就开始调用远程接口。
从远程获取数据
startWithoutCache
- (void)startWithoutCache {
[self clearCacheVariables];
[super start];
}
先清除缓存数据,再开始请求。
- (void)start {
[self toggleAccessoriesWillStartCallBack];
[[YTKNetworkAgent sharedAgent] addRequest:self];
}
YTKNetworkAgent
的初始化
- (instancetype)init {
self = [super init];
if (self) {
_config = [YTKNetworkConfig sharedConfig];
_manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:_config.sessionConfiguration];
_requestsRecord = [NSMutableDictionary dictionary];
_processingQueue = dispatch_queue_create("com.yuantiku.networkagent.processing", DISPATCH_QUEUE_CONCURRENT);
_allStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(100, 500)];
pthread_mutex_init(&_lock, NULL);
_manager.securityPolicy = _config.securityPolicy;
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// Take over the status code validation
_manager.responseSerializer.acceptableStatusCodes = _allStatusCodes;
_manager.completionQueue = _processingQueue;
}
return self;
}
初始化的过程做了以下事情:
- 1、初始化YTKNetworkConfig,YTKNetworkConfig主要保存了baseUrl、cdnUrl等等,还可以这这里配置NSURLSessionConfiguration,用来初始化AFHTTPSessionManager;
- 2、初始化AFHTTPSessionManager;
- 3、初始化保存请求记录的容器;
- 4、创建一个并行队列,用于配置AFHTTPSessionManager的completionQueue,因为AFHTTPSessionManager默认在主队列执行completionBlock;
- 5、初始化合法的HttpCode,AFHTTPSessionManager会根据这个来判断结果是否有效从而决定请求是成功还是失败,这里允许所有的返回码;
- 6、创建互斥锁;
- 7、配置AFHTTPSessionManager的相关属性。
添加请求的代码addRequest
:
- (void)addRequest:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
NSError * __autoreleasing requestSerializationError = nil;
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 {
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]));
[self addRequestToRecord:request];
[request.requestTask resume];
}
- 1、这一步的主要工作包括要序列化
NSURLRequest
,而YTKBaseRequest有提供创建自定义NSURLRequest的方法,如果创建了自定义的NSURLRequest就直接调用AFHTTPSessionManager
创建NSURLSessionDataTask
发起请求。这里用AFHTTPSessionManager可以省去自己处理请求进度和回调处理等工作,只需处理最后的返回结果即可; - 2、如果没有创建自定义的NSURLRequest,就会走YTKNetwork的方法:
[self sessionTaskForRequest:request error:&requestSerializationError]
:
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
YTKRequestMethod method = [request requestMethod];
NSString *url = [self buildRequestUrl:request];
id param = request.requestArgument;
AFConstructingBlock constructingBlock = [request constructingBodyBlock];
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];
}
}
首先需要关注一下创建url的代码NSString *url = [self buildRequestUrl:request];
:
- (NSString *)buildRequestUrl:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
NSString *detailUrl = [request requestUrl];
NSURL *temp = [NSURL URLWithString:detailUrl];
// If detailUrl is valid URL
if (temp && temp.host && temp.scheme) {
return detailUrl;
}
// Filter URL if needed
NSArray *filters = [_config urlFilters];
for (id<YTKUrlFilterProtocol> f in filters) {
detailUrl = [f filterUrl:detailUrl withRequest:request];
}
NSString *baseUrl;
if ([request useCDN]) {
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:@""];
}
return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
}
- 1、如果直接在YTKBaseRequest里面配置了完整的url,则直接返回;
- 2、对url进行自定义的处理;
- 3、获取baseUrl,以单个请求优先,取不到再去YTKNetworkConfig中取;
- 4、拼接完整的地址。
至此拿到了完整的请求url,再回到创建NSURLSessionTask的代码,接下来获取用于序列化请求的requestSerializerAFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request]
:
- (AFHTTPRequestSerializer *)requestSerializerForRequest:(YTKBaseRequest *)request {
AFHTTPRequestSerializer *requestSerializer = nil;
if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
requestSerializer = [AFHTTPRequestSerializer serializer];
} else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
requestSerializer = [AFJSONRequestSerializer serializer];
}
requestSerializer.timeoutInterval = [request requestTimeoutInterval];
requestSerializer.allowsCellularAccess = [request allowsCellularAccess];
// If api needs server username and password
NSArray<NSString *> *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
if (authorizationHeaderFieldArray != nil) {
[requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFieldArray.firstObject
password:authorizationHeaderFieldArray.lastObject];
}
// If api needs to add custom value to HTTPHeaderField
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;
}
- 1、根据YTKBaseRequest指定的序列化方法创建requestSerializer;
- 2、设置用户认证信息;
- 3、设置自定义的头部信息。
拿到requestSerializer后开始调用相应的方法创建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];
}
如果resumableDownloadPath不为空则说明是下载,会调用创建下载任务的方法,否则就是普通的请求,看看dataTaskWithHTTPMethod
方法:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError * _Nullable __autoreleasing *)error {
return [self dataTaskWithHTTPMethod:method requestSerializer:requestSerializer URLString:URLString parameters:parameters constructingBodyWithBlock:nil error:error];
}
- (NSURLSessionDataTask *)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;
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;
}
调用AFHTTPSessionManager
的dataTaskWithRequest
创建请求,在回调中通过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;
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、获取当前的请求对象,如果请求为空,也就是被取消了,则不做任何处理,直接返回;
- 2、判断responseObject是否是NSData,理论上应该是NSData,因为在初始化
AFHTTPSessionManager
时设置了responseSerializer就是[AFHTTPResponseSerializer serializer]
,而AFHTTPSessionManager是直接返回NSData:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
- 3、根据responseSerializerType解析数据;
- 4、判断返回结果是否合法,首先根据YTKBaseRequest定义的合法的
statusCode
来判断,然后再根据jsonValidator
来判断返回的json数据格式是否正确。 - 5、根据请求成功与否来进行处理。
请求成功后的处理:
- (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];
});
}
- 1、调用requestCompletePreprocessor方法进去处理,这里YTKBaseRequest没有做任何事情,主要是在YTKRequest里面执行缓存的处理:
- (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);
}
}
}
}
至此从远程获取数据并保存到本地的流程就走完了。
网友评论