YTKNetwork是猿题库 iOS 研发团队基于 AFNetworking 封装的 iOS 网络库,提供了更高层次的网络访问抽象。
相比于AFNetworking,YTKNetwork提供了更高级的功能
·支持按时间缓存网络请求内容
·支持按版本号缓存网络请求内容
·支持统一设置服务器和 CDN 的地址
·支持检查返回 JSON 内容的合法性
·支持文件的断点续传
·支持 block 和 delegate 两种模式的回调方式
·支持批量的网络请求发送,并统一设置它们的回调(实现在 YTKBatchRequest 类中)
·支持方便地设置有相互依赖的网络请求的发送,例如:发送请求 A,根据请求 A 的结果,选择性·的发送请求 B 和 C,再根据 B 和 C 的结果,选择性的发送请求 D。(实现在 YTKChainRequest 类中)
·支持网络请求 URL 的 filter,可以统一为网络请求加上一些参数,或者修改一些路径。
定义了一套插件机制,可以很方便地为 YTKNetwork 增加功能。猿题库官方现在提供了一个插件,可以在某些网络请求发起时,在界面上显示“正在加载”的 HUD。
YTKNetwork使用了设计模式里面的command模式,简单来说,就是把请求封装成一个对象,使需要请求的类与第三方底层网络分开,降低耦合性,也方便了我们更换网络库。同时也方便处理一些处理公共逻辑和对象的持久化。
接下来我们来看看YTK是怎么工作的。
在github的基础教程里面我们可以看到YTK是把每个网络请求都封装成对象,所以我们每个请求都需要继承YTKRequest,通过重写父类的方法来构造我们想要的网络请求,所以我们来重写一下YTKRequest从而进行一个网络请求
#import
@interface ZJRequest : YTKRequest
@end
#import "ZJRequest.h"
@implementation ZJRequest
- (NSString *)requestUrl
{
return @"/api/4/news/latest";
}
- (YTKRequestMethod)requestMethod
{
return YTKRequestMethodGET;
}
- (id)requestArgument
{
return @{};
}
@end
这里我们requestUrl去掉了域名信息,因为域名信息我们已经在AppDelegate利用YTKNetworkConfig设置过域名。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
YTKNetworkConfig *config = [YTKNetworkConfig sharedConfig];
config.baseUrl = @"https://news-at.zhihu.com";
return YES;
}
上述代码我们重写了参数和请求方式方法,使用GET方法来请求。
我们构造完成ZJRequest之后,来我们需要网络请求的ViewController,调用我们的ZJRequest,并使用block来取得网络请求结果
ZJRequest *request = [[ZJRequest alloc]init];
[request startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest * _Nonnull request) {
NSLog(@"succeed");
} failure:^(__kindof YTKBaseRequest * _Nonnull request) {
NSLog(@"failure");
}];
除此之外,YTKNetwork还支持delegate回调
- (void)viewDidLoad {
[super viewDidLoad];
ZJRequest *request = [[ZJRequest alloc]init];
// [request startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest * _Nonnull request) {
// NSLog(@"succeed");
// } failure:^(__kindof YTKBaseRequest * _Nonnull request) {
// NSLog(@"failure");
// }];
request.delegate = self;
[request start];
}
- (void)requestFinished:(YTKBaseRequest *)request {
NSLog(@"succeed");
}
- (void)requestFailed:(YTKBaseRequest *)request {
NSLog(@"failed");
}
我们先从startWithCompletionBlockWithSuccess:failure
点进去看看里面的实现
- (void)startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
[self setCompletionBlockWithSuccess:success failure:failure];
[self start];
}
YTK先设置了成功的block和失败的回调,具体是用setter方法把YTKBaseRequest里面的successCompletionBlock
和failureCompletionBlock
赋值进去
- (void)setCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
self.successCompletionBlock = success;
self.failureCompletionBlock = failure;
}
接下来调用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];
YTKRequest *strongSelf = self;
[strongSelf.delegate requestFinished:strongSelf];
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
});
}
YTK使用了三个判断,都是是否需要进行无缓存的请求
如果我们的缓存里面已经有响应了,YTK就会调用requestCompletePreprocessor
和requestCompleteFilter
两个方法,但这两个方法都是留给我们去实现,YTK并没有实现,然后并调用了自身的完成回调并传递为成功回调
我们先假设缓存为nil,进入startWithoutCache
看看
- (void)startWithoutCache {
//先清除所有缓存的变量
[self clearCacheVariables];
[super start];
}
- (void)clearCacheVariables {
_cacheData = nil;//
_cacheXML = nil;
_cacheJSON = nil;
_cacheString = nil;
_cacheMetadata = nil;
_dataFromCache = NO;
}
YTK先清除缓存的所有变量,包括响应的Data数据,XML,JSON,String,元Data,还有一个布尔值,方便我们做响应的存储,分别对应各种数据格式
接下来调用父类YTKBaseRequest的重点方法start
- (void)start {
[self toggleAccessoriesWillStartCallBack];
[[YTKNetworkAgent sharedAgent] addRequest:self];
}
- (void)toggleAccessoriesWillStartCallBack {
for (id accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStart:)]) {
[accessory requestWillStart:self];
}
}
}
第一个方法就是调用协议里面的requestWillStart:
来通知请求即将开始。其中self.requestAccessories
是YTKBaseRequest定义的一个可变数组,用来储存协议YTKRequestAccessory
,第二个方法即是通过YTKNetworkAgent的单例调用addRequest
方法
我们来看这个单例是什么
+ (YTKNetworkAgent *)sharedAgent {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (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;
}
这个类包括创建了YTKNetworkConfig
的单例,还有AFHTTPSessionManager
的实例并把单例内的sessionConfig传进AFHTTPSessionManager,还有相应的安全政策,设定可接受响应码,并创建了一个并行队列设置为AF的完成队列和互斥锁lock
然后我们进入addRequest:
这个方法
- (void)addRequest:(YTKBaseRequest *)request {
//参数判断request不能为空
NSParameterAssert(request != nil);
NSError * __autoreleasing requestSerializationError = nil;
//这里如果我们设置了url就直接用url请求
NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
if (customUrlRequest) {
__block NSURLSessionDataTask *dataTask = nil;
//调用AF请求
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]));
//绑定request的id,这里跟AF的思想是一样的,用一个字典存储请求,并设置一个id为key
[self addRequestToRecord:request];
[request.requestTask resume];
}
这里面涉及的方法很多,我们来一个一个看
首先YTK先获取CustomRequest,如果我们重写了一个request则直接调用AF请求,并设置响应内容调用了handleRequestResult:responseObject:error
,记录了dataTask。反之,则用request的参数通过sessionTaskRequest:error
自己生成了一个requestTask,再用requestRecord
记录了这个request,最后resume这个task。
AF的方法在这就暂不描述,感兴趣可以看这篇AFNetworking解析,它做了什么
我们来看回调里面YTK调用的方法
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
//避免多线程下取到的request不一致,这里用了线程锁
Lock();
YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
Unlock();
//在AF中,request如果被取消或者在record里被删除,将会调用失败回调,在YTK则直接返回,
不做回调
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;
//根据响应内容的编码做转换,其中调用了AF2.6.3中方法
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];
//YTK在这里把回调都置为nil,从而打破循环引用,所以我们可以直接在最开始的startWithCompletionBlockWithSuccess:failure:里面的block直接使用self
[request clearCompletionBlock];
});
}
这段代码很容易看懂,就是获取到响应内容,并做序列化,最后再返回序列化后的内容
其中我们可以通过重写YTK的responseSerializerType分别对数据进行解析,分为HTTP(不做处理),JSON,XML,使用的都是AF里面的方法。YTK最后还对响应的内容做了合法化判断(如果你重写了jsonValidator
),我们可以看看这个方法做了什么
- (BOOL)validateResult:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
//判断响应码是否在200~299
BOOL result = [request statusCodeValidator];
if (!result) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidStatusCode userInfo:@{NSLocalizedDescriptionKey:@"Invalid status code"}];
}
return result;
}
id json = [request responseJSONObject];
id validator = [request jsonValidator];
//获取响应的json和需要验证的字段
if (json && validator) {
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;
}
我们接着看validateJSON:withValidator:
+ (BOOL)validateJSON:(id)json withValidator:(id)jsonValidator {
if ([json isKindOfClass:[NSDictionary class]] &&
[jsonValidator isKindOfClass:[NSDictionary class]]) {
NSDictionary * dict = json;
NSDictionary * validator = jsonValidator;
BOOL result = YES;
NSEnumerator * enumerator = [validator keyEnumerator];
NSString * key;
while ((key = [enumerator nextObject]) != nil) {
id value = dict[key];
id format = validator[key];
if ([value isKindOfClass:[NSDictionary class]]
|| [value isKindOfClass:[NSArray class]]) {
result = [self validateJSON:value withValidator:format];
if (!result) {
break;
}
} else {
if ([value isKindOfClass:format] == NO &&
[value isKindOfClass:[NSNull class]] == NO) {
result = NO;
break;
}
}
}
return result;
} else if ([json isKindOfClass:[NSArray class]] &&
[jsonValidator isKindOfClass:[NSArray class]]) {
NSArray * validatorArray = (NSArray *)jsonValidator;
if (validatorArray.count > 0) {
NSArray * array = json;
NSDictionary * validator = jsonValidator[0];
for (id item in array) {
BOOL result = [self validateJSON:item withValidator:validator];
if (!result) {
return NO;
}
}
}
return YES;
} else if ([json isKindOfClass:jsonValidator]) {
return YES;
} else {
return NO;
}
这个方法就是递归调用遍历jsonValidator和json看数据类型是否跟我们要求的一样,方法很简单清晰
我们再回到handleRequestResult:responseObject:error:
这个方法里面
if (succeed) {
[self requestDidSucceedWithRequest:request];
} else {
[self requestDidFailWithRequest:request error:requestError];
}
我们来看succeed为YES的时候YTK做了什么
- (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];
});
}
- (void)requestCompletePreprocessor {
[super requestCompletePreprocessor];
//是否要异步写入缓存
if (self.writeCacheAsynchronously) {
//创建了一个异步队列单例
dispatch_async(ytkrequest_cache_writing_queue(), ^{
[self saveResponseDataToCacheFile:[super responseData]];
});
} else {
[self saveResponseDataToCacheFile:[super responseData]];
}
}
当succeed为YES的时候YTK首先调用了requestCompletePreprocessor
把请求回来的数据做了一个缓存,然后调用requestWillStop:
和requestCompleteFilter
,这两个方法都没有具体的实现,是想我们自己实现吧,接下来分别调用requestFinished
和成功完成的回调和协议。
我们来看YTK是如何做缓存的,缓存策略是什么
- (void)saveResponseDataToCacheFile:(NSData *)data {
//缓存时间存在和要缓存的数据不是来自缓存
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);
}
}
}
}
- (NSString *)cacheBasePath {
NSString *pathOfLibrary = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [pathOfLibrary stringByAppendingPathComponent:@"LazyRequestCache"];
//如果自定义了缓存地址,则使用自定义地址缓存
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;
}
- (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 {
//禁止icloud备份
[YTKNetworkUtils addDoNotBackupAttribute: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 *)cacheFilePath {
NSString *cacheFileName = [self cacheFileName];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheFileName];
return path;
}
- (NSString *)cacheMetadataFilePath {
NSString *cacheMetadataFileName = [NSString stringWithFormat:@"%@.metadata", [self cacheFileName]];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheMetadataFileName];
return path;
}
YTK缓存文件名由请求方法,完整的url地址和参数,并做md5变换。先创建地址名为“LazyRequestCache”缓存的文件夹,最后再把数据写进文件。然后把创建了一个YTKCacheMetadata的实例,专门用来储存当前这个缓存的附加信息,也存在了跟data相同的文件夹里面
接下来来看Succeed为NO的时候
- (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);
//如果是下载的数据,则先对下载的数据做缓存。因为可能是序列化数据的时候出现了问题,所以先对下载的数据做缓存,其中resumeableDownloadPath的为自定义路径,提供恢复下载的地址
NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
if (incompleteDownloadData) {
[incompleteDownloadData writeToURL:[self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] 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];
}
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];
});
}
总的来说,如果有下载的数据,就先做缓存,下载失败,则缓存url。最后在分别调用requestFailedPreprocessor
和requestFailedFilter
,具体实现YTK留给用户实现,然后调用失败回调。
回到我们的addRequest:
方法里面
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];
}
我们来看YTK创建requestTask的方法
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
//获取request必要的参数
YTKRequestMethod method = [request requestMethod];
NSString *url = [self buildRequestUrl:request];
id param = request.requestArgument;
//此处block是为了上传图片等附件形式
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];
}
}
前面是获取request的一些必要参数,在获取url的获取,YTK用了一个方法buildRequestUrl:
,里面做了对Url的合法性判断,还有是否需要cdn加速。接下来获取了request的序列化类的实例,并准备开始请求。我们先来看dataTaskWithHTTPMethod:requestSerializer:URLString:parameters:error:
这个通用方法,YTK传入了请求方法、序列化方式、参数和url,如果为post方法还加入了constructingBlock。
- (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 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;
}
如果是文件上传的方式,则调用AF的文件上传方法,反之,则使用普通的请求方式。
总结,YTKbaseRequest就是把我们每个请求都封装成一个对象,具体的实现请求都交给了AFNetworking。代码还是结构还是很清晰的,阅读难度也不大
接下我们来看YTK的YTKBatchRequest类
YTKBatchRequest可以统一处理多个请求
用法如下
- (void)sendBatchRequest {
GetImageApi *a = [[GetImageApi alloc] initWithImageId:@"1.jpg"];
GetImageApi *b = [[GetImageApi alloc] initWithImageId:@"2.jpg"];
GetImageApi *c = [[GetImageApi alloc] initWithImageId:@"3.jpg"];
GetUserInfoApi *d = [[GetUserInfoApi alloc] initWithUserId:@"123"];
YTKBatchRequest *batchRequest = [[YTKBatchRequest alloc] initWithRequestArray:@[a, b, c, d]];
[batchRequest startWithCompletionBlockWithSuccess:^(YTKBatchRequest *batchRequest) {
NSLog(@"succeed");
NSArray *requests = batchRequest.requestArray;
GetImageApi *a = (GetImageApi *)requests[0];
GetImageApi *b = (GetImageApi *)requests[1];
GetImageApi *c = (GetImageApi *)requests[2];
GetUserInfoApi *user = (GetUserInfoApi *)requests[3];
// deal with requests result ...
} failure:^(YTKBatchRequest *batchRequest) {
NSLog(@"failed");
}];
}
我们来看下init方法
- (instancetype)initWithRequestArray:(NSArray *)requestArray {
self = [super init];
if (self) {
_requestArray = [requestArray copy];
_finishedCount = 0;
for (YTKRequest * req in _requestArray) {
if (![req isKindOfClass:[YTKRequest class]]) {
YTKLog(@"Error, request item must be YTKRequest instance.");
return nil;
}
}
}
return self;
}
初始化方法中,先取得request的数组requestArray,并且设置完成请求数量finishCount为0。
接下来来看
- (void)startWithCompletionBlockWithSuccess:(void (^)(YTKBatchRequest *batchRequest))success
failure:(void (^)(YTKBatchRequest *batchRequest))failure {
[self setCompletionBlockWithSuccess:success failure:failure];
[self start];
}
- (void)setCompletionBlockWithSuccess:(void (^)(YTKBatchRequest *batchRequest))success
failure:(void (^)(YTKBatchRequest *batchRequest))failure {
self.successCompletionBlock = success;
self.failureCompletionBlock = failure;
}
跟YTKBaseRequest一样,都是先设置好回调的Block,再调用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];
}
先判断时候有正在进行的request,如果有就返回,没有的话,遍历requestArray里面的内容,并开始请求,并设置了delegate
我们来看回调
- (void)requestFinished:(YTKRequest *)request {
_finishedCount++;
if (_finishedCount == _requestArray.count) {
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(batchRequestFinished:)]) {
[_delegate batchRequestFinished:self];
}
if (_successCompletionBlock) {
_successCompletionBlock(self);
}
[self clearCompletionBlock];
[self toggleAccessoriesDidStopCallBack];
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
}
- (void)requestFailed:(YTKRequest *)request {
_failedRequest = request;
[self toggleAccessoriesWillStopCallBack];
// Stop
for (YTKRequest *req in _requestArray) {
[req stop];
}
// Callback
if ([_delegate respondsToSelector:@selector(batchRequestFailed:)]) {
[_delegate batchRequestFailed:self];
}
if (_failureCompletionBlock) {
_failureCompletionBlock(self);
}
// Clear
[self clearCompletionBlock];
[self toggleAccessoriesDidStopCallBack];
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
- (void)clearRequest {
for (YTKRequest * req in _requestArray) {
[req stop];
}
[self clearCompletionBlock];
}
当所有请求完成并成功,返回所有request,并清除block。里面利用finishCount来判断是否请求都完成,如果是有一个请求失败的话,则暂停所有请求,返回失败回调。