YTKNetwork
是对AFNetworking
的二次封装, 是离散型网络请求层的典型代表.
它的特点就是把每一个网络请求都抽象成一个对象.
这些网络请求的基类就是YTKBaseRequest
.
试想一下, 一个完整网络请求需要哪些要素:
- 请求地址
- 请求头
- 请求体
- 发起请求
- 收到服务器的响应头
- 响应体
那么这个YTKBaseRequest
就是将所有这些进行了封装
在发起请求的时候, 我们需要实例化一个request
对象, 重写一些方法:
///=============================================================================
/// @name 子类重写
///=============================================================================
///< 请求成功后, 切换到主线程之前, 在子线程调用
///< 注意: 如果是缓存加载, 这个方法将在主线程调用, 和`requestCompleteFilter`方法一样
- (void)requestCompletePreprocessor;
///< 请求成功后在主线程调用
- (void)requestCompleteFilter;
///< 请求失败后, 切换到主线程之前, 在子线程调用
///< 注意: 如果是缓存加载, 这个方法将在主线程调用
- (void)requestFailedPreprocessor;
///< 请求失败后在主线程调用
- (void)requestFailedFilter;
///< 请求的baseURL, 只包含URL的主机名部分, 例如, http://www.example.com.
- (NSString *)baseUrl;
///< 请求URL的路径部分, 只包含URL的路径部分, 例如:/v1/user
- (NSString *)requestUrl;
///< CDN URL
- (NSString *)cdnUrl;
///< 请求的超时时长, 默认60秒
///< 当设置了`resumableDownloadPath`(NSURLSessionDownloadTask)属性时, 会话会忽略`timeoutInterval`属性, 一种有效的设置超时时长的方法是设置`NSURLSessionConfiguration`的`timeoutIntervalForResource`属性.
- (NSTimeInterval)requestTimeoutInterval;
///< 额外的请求参数
- (nullable id)requestArgument;
/// Override this method to filter requests with certain arguments when caching.
- (id)cacheFileNameFilterForRequestArgument:(id)argument;
/// HTTP 请求方法 "GET", "POST"这些
- (YTKRequestMethod)requestMethod;
/// Request 请求序列化类型 "HTTP", "JSON"
- (YTKRequestSerializerType)requestSerializerType;
/// Response 响应序列化类型 "HTTP", "JSON"
- (YTKResponseSerializerType)responseSerializerType;
///< HTTP授权要使用的用户名和密码, 必须写成@[@"Username", @"Password"]这种格式
- (nullable NSArray<NSString *> *)requestAuthorizationHeaderFieldArray;
/// 添加的请求头信息
- (nullable NSDictionary<NSString *, NSString *> *)requestHeaderFieldValueDictionary;
///< 自定义一个`NSURLRequest`对象, 当这个返回不为空时,`requestUrl`, `requestTimeoutInterval`,
/// `requestArgument`, `allowsCellularAccess`, `requestMethod`和 `requestSerializerType`方法将被忽略
- (nullable NSURLRequest *)buildCustomUrlRequest;
/// 发送请求时是否使用CDN
- (BOOL)useCDN;
/// 发起请求时是否使用蜂窝网络, 默认是YES
- (BOOL)allowsCellularAccess;
/// 检测返回的json对象是不是标准json格式
- (nullable id)jsonValidator;
/// 检测响应状态码是否有效
- (BOOL)statusCodeValidator;
重写了这些方法以后, 一个HTTP基本请求的要求就满足了, 有请求地址, 请求头request
.
然后就是发起请求了:
基类里面提供了很简单的三个方法:
///=============================================================================
/// @name 请求行为
///=============================================================================
/// 添加自己到请求队列, 并发起请求
- (void)start;
/// 将自己移除出请求队列, 并终止请求
- (void)stop;
/// 发起请求, 包含成功和失败回调
- (void)startWithCompletionBlockWithSuccess:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure;
发起请求和终止请求的实现:
- (void)start {
[self toggleAccessoriesWillStartCallBack];
[[YTKNetworkAgent sharedAgent] addRequest:self];
}
- (void)stop {
[self toggleAccessoriesWillStopCallBack];
self.delegate = nil;
[[YTKNetworkAgent sharedAgent] cancelRequest:self];
[self toggleAccessoriesDidStopCallBack];
}
这里面的核心方法就是[[YTKNetworkAgent sharedAgent] addRequest:self]
和[[YTKNetworkAgent sharedAgent] cancelRequest:self]
.
这里就涉及到YTKNetworkAgent
这个单例对象了, 单例对象的核心方法就这两个:
/// 将请求添加进会话, 并发起请求
- (void)addRequest:(YTKBaseRequest *)request;
/// 将之前添加进会话的请求取消
- (void)cancelRequest:(YTKBaseRequest *)request;
- (void)addRequest:(YTKBaseRequest *)request;
方法正如注释上写的, 就做了两件是
- 将请求添加进会话, 返回
task
对象 - 发起请求,
[task resume]
不过细看下来,当然没那么简单, 里面做了很多逻辑. 先看添加请求进会话, 发起请求这个方法:
-
首先看了
buildCustomUrlRequest
这个方法有没有重写
- 如果重写了, 就直接根据自定义的request
创建task
, 直接调用AFHTTPSessionManager
的方法去创建task
, 然后在完成回调里面处理数据, 填充request
的response
数据等等.
- 如果没重写, 那么就要重新创建task
对象, 间接调用AFHTTPSessionManager
的方法, 但本质上都是调用AFN
, 因为YTKNetwork
本身就是对AFN
的二次封装. -
iOS 8.0以上系统, 还会对
task
设定优先级 -
将
request
对象添加进一个字典做内存缓存 -
发起请求
[request.requestTask resume]
- (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];
}
然后看取消请求的方法:
- (void)cancelRequest:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
if (request.resumableDownloadPath) {
NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
[requestTask cancelByProducingResumeData:^(NSData *resumeData) {
NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
[resumeData writeToURL:localUrl atomically:YES];
}];
} else {
[request.requestTask cancel];
}
[self removeRequestFromRecord:request];
[request clearCompletionBlock];
}
- 核心方法就是调用
[task cancel]
方法 - 然后将请求移除出缓存
最后还有一个取消所有请求方法, 思路就是将缓存中的请求遍历, 然后逐一取消.
前面说了, YTKNetwork
是一个离散型的网络框架, 那么, 它的每一个请求其实都是继承自YTKRequest
的, 而不是YTKBaseRequest
, YTKRequest
相对于YTKBaseRequest
增加了缓存处理.
当你继承自YTKRequest
的时候, 可以通过重写父类方法, 自定义一些缓存策略.
下面是子类需要去重写的方法:
///< 当你创建自己的请求类的时候, 你必须继承YTKRequest,YTKRequest又继承自YTKBaseRequest
///< YTKRequest在YTKBaseRequest的基础上添加了本地缓存特征
///< 但需要注意的是, 下载请求不会做缓存, 因为下载请求涉及到复杂的缓存控制策略, 比如`Cache-Control`, `Last-Modified`的控制
@interface YTKRequest : YTKBaseRequest
///< 是否缓存响应
///< 默认为NO, 当遇到一些特殊的参数时, 缓存才会起作用
///< cacheTimeInSeconds 默认 -1秒, 缓存数据不会使用,除非你返回一个正值
///< 如果这个属性是YES, 那么响应数据将一直被存储
@property (nonatomic) BOOL ignoreCache;
/// 数据是否是从本地缓存中来的
- (BOOL)isDataFromCache;
/// 手动加载缓存
///
/// 缓存加载失败, error有值, 否则为NULL
///
/// @return 缓存是否成功被加载
- (BOOL)loadCacheWithError:(NSError * __autoreleasing *)error;
///< 不管本地是否有缓存都发起请求, 使用这个方法更新本地缓存
- (void)startWithoutCache;
/// 保存响应数据到本地的缓存路径
- (void)saveResponseDataToCacheFile:(NSData *)data;
#pragma mark - Subclass Override
///< 默认为 -1. 表示不缓存
///< 设置最大的磁盘缓存时间
- (NSInteger)cacheTimeInSeconds;
///< 可以用来确定本地缓存, 和让本地缓存失效, 默认是0
- (long long)cacheVersion;
///< 用来告诉缓存需要跟新的额外标识符, 推荐返回数组或者字典
- (nullable id)cacheSensitiveData;
/// 异步写缓存, 默认为YES
- (BOOL)writeCacheAsynchronously;
在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];
});
}
这里进行了三步操作
-
先判断
ignoreCache
属性是否为YES
, 如果为YES
, 表明是忽略缓存, 那么直接将所有缓存变量置空, 并调用父类的start
方法. -
判断是否指定了
resumableDownloadPath
路径, 如果制定了路径, 说明是下载任务, 下载任务是不做缓存的. -
加载缓存, 如果加载成功, 则拿到缓存数据, 走
finish
代理, 已经成功block
回调, 如果加载失败, 则调父类的start
方法.
加载缓存成功之后, 在主线程调用requestCompletePreprocessor
方法, 这个方法内部会将数据缓存起来:
- (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);
}
}
}
}
既要存储数据(写文件
的方式存储), 也要存储元数据
, 元数据
中包含了缓存的版本
, 编码方式
,创建时间
, 等等, 元数据用归档
的方式存储.
基本的关系图如下图所示:
YTKNetwork各类关系图
YTKNetwork
的批量请求(YTKBatchRequest)
和链式请求(YTKChainRequest)
- YTKBatchRequest
批量请求
代码实现其实并不复杂
YTKBatchRequest
是将一组YTKRequest
打包初始化的
- (instancetype)initWithRequestArray:(NSArray<YTKRequest *> *)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
开始发起请求或者取消请求, 代码如下:
- (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];
}
}
- (void)stop {
[self toggleAccessoriesWillStopCallBack];
_delegate = nil;
[self clearRequest];
[self toggleAccessoriesDidStopCallBack];
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
上面两个Api
也可以直接调用
YTKBatchRequest
数组里面每一个YTKRequest
请求的或成功, 或失败, 都会走
- (void)requestFinished:(YTKRequest *)request
以及
- (void)requestFailed:(YTKRequest *)request
这两个代理回调, 代码如下:
- (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];
}
每单个YTKRequest
请求结果回来都会走上面两个代理方法
-
如果走了成功的代理回调, 那么
_finishedCount
就会自增.
当_finishedCount
变量的值等于请求数组_requestArray
的count
值时, 表明所有请求都成功了, 此时调用batchRequestFinished:
代理方法以及_successCompletionBlock
成功的block
回调 -
如果走了失败的代理回调, 只要有一个请求失败, 所有请求都取消:
for (YTKRequest *req in _requestArray) {
[req stop];
}
然后调用batchRequestFailed:
代理方法, 以及_failureCompletionBlock
失败的代理回调
- YTKChainRequest
链式请求
- 通过
- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback
方法, 将request
请求和callback
回调方法都用数组保存起来
- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback {
[_requestArray addObject:request];
if (callback != nil) {
[_requestCallbackArray addObject:callback];
} else {
[_requestCallbackArray addObject:_emptyCallback];
}
}
- 在调用
start
内部会调用startNextRequest
方法:
- (BOOL)startNextRequest {
if (_nextRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[_nextRequestIndex];
_nextRequestIndex++;
request.delegate = self;
[request clearCompletionBlock];
[request start];
return YES;
} else {
return NO;
}
}
在这个方法中, 会根据数组角标取出当前的request
对象, 发起请求[request start];
, 然后会走- (void)requestFinished:(YTKBaseRequest *)request
代理回调, 在代理方法中,又会调用startNextRequest
方法, 形成了一个递归调用, 直到startNextRequest
方法返回NO
.
- 此时, 整个链式调用完毕, 走
chainRequestFinished:
代理回调方法:
- (void)requestFinished:(YTKBaseRequest *)request {
NSUInteger currentRequestIndex = _nextRequestIndex - 1;
YTKChainCallback callback = _requestCallbackArray[currentRequestIndex];
callback(self, request);
if (![self startNextRequest]) {
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(chainRequestFinished:)]) {
[_delegate chainRequestFinished:self];
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
}
[self toggleAccessoriesDidStopCallBack];
}
}
以上就是我对YTKNetwork
源码的分析
YTKNetwork
源码
网友评论