背景:
YTKNetWork是一个开源的第三方网络请求框架,具有比较好的网络请求缓存机制的控制。近期项目中想要采取HTTP Cache来优化网络请求,需要实现以下两点:
- 需要支持解析 Cache-Control Header, 包括 max-age 指令 和 no-cache 指令来实现强制缓存(即不需要向服务器发请求, 直接使用本地缓存)。
- 需要支持解析 Etag, 发送 If-None-Match, 来实现对比缓存。
经过简单的调研发现,YTKNetWork虽然底层是使用的AFNetWorking的框架,但是使用AFNetWorking能够通过设置缓存空间和缓存协议就能快速简单实现的方式在YTKNetWork中并没有生效。YTKNetWork已经很少维护了,所以,只能自己动手来分析YTKNetWork的实现。
废话不多说,下面就开始吧。
一、项目结构分析
先上两张图
首先,我们从目录中找到YTKNetWork.h的头文件,从中可以看到作者想对我们开放的基础功能模块,根据字面意思可以分为几种,一种是Request类型的,一种是Config,还一种是Agent。很容易理解,request类型的是用来发送网络请求的,config是用来配置请求信息的,agent暂时不清楚,用到时我们再来具体分析。那么项目结构就很清晰了,我们就一步一步来分析就好了。
二、YTKNetworkConfig
我们从这个最简单的单体类来入手。
#import
NS_ASSUME_NONNULL_BEGIN
@class YTKBaseRequest;
@class AFSecurityPolicy;
-- 声明了YTKUrlFilterProtocol协议,用来在发送请求之前给请求添加普通的参数
@protocol YTKUrlFilterProtocol
/// Preprocess request URL before actually sending them.
///
/// @param originUrl request's origin URL, which is returned by `requestUrl`
/// @param request request itself
///
/// @return A new url which will be used as a new `requestUrl`
- (NSString *)filterUrl:(NSString *)originUrl withRequest:(YTKBaseRequest *)request;
@end
-- 声明了YTKCacheDirPathFilterProtocol协议,在缓存请求结果的时候用来追加普通的路径信息
@protocol YTKCacheDirPathFilterProtocol
/// Preprocess cache path before actually saving them.
///
/// @param originPath original base cache path, which is generated in `YTKRequest` class.
/// @param request request itself
///
/// @return A new path which will be used as base path when caching.
- (NSString *)filterCacheDirPath:(NSString *)originPath withRequest:(YTKBaseRequest *)request;
@end
-- YTKNetworkConfig这个类保存了全局的网络请求配置信息,会在YTKNetworkAgent中使用,可以格式化以及过滤请求,还可以缓存响应结果
@interface YTKNetworkConfig : NSObject
-- 这两个方法不能使用
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
-- 使用类方法创建单例对象
+ (YTKNetworkConfig *)sharedConfig;
-- 请求的根URL,默认是空字符串
@property (nonatomic, strong) NSString *baseUrl;
-- 请求CDN URL,默认是空字符串
@property (nonatomic, strong) NSString *cdnUrl;
-- URL过滤池(YTKUrlFilterProtocol协议使用)
@property (nonatomic, strong, readonly) NSArray> *urlFilters;
-- 缓存路径的过滤池(YTKCacheDirPathFilterProtocol协议使用)
@property (nonatomic, strong, readonly) NSArray> *cacheDirPathFilters;
-- 同AFNetworking中使用的安全策略
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
-- 是否记录调试信息,默认是NO
@property (nonatomic) BOOL debugLogEnabled;
-- 用来初始化AFHTTPSessionManager,默认是nil
@property (nonatomic, strong) NSURLSessionConfiguration* sessionConfiguration;
-- 添加一个新的URL过滤器
- (void)addUrlFilter:(id)filter;
-- 删除所有的URL过滤器
- (void)clearUrlFilter;
-- 添加一个新的缓存地址过滤器
- (void)addCacheDirPathFilter:(id)filter;
-- 删除所有的缓存地址过滤器
- (void)clearCacheDirPathFilter;
@end
NS_ASSUME_NONNULL_END
m文件中没什么可以分析的,就是对两个过滤池数组的增删操作,唯一注意的就是AFSecurityPolicy初始化的时候使用了defaultPolicy类方法实例化
- (instancetype)init {
self = [super init];
if (self) {
_baseUrl = @"";
_cdnUrl = @"";
_urlFilters = [NSMutableArray array];
_cacheDirPathFilters = [NSMutableArray array];
_securityPolicy = [AFSecurityPolicy defaultPolicy];
_debugLogEnabled = NO;
}
return self;
}
-- 点进去我们可以看到就是默认不使用SSL安全策略
+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
return securityPolicy;
}
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
总结下来,YTKNetworkConfig这个类的作用就是设置base URL、安全策略、url的过滤、缓存路径的过滤。
但是现在对于两个协议的具体使用方式还有疑问,我们带着这个疑问继续往下看。
三、YTKBaseRequest
#import
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *const YTKRequestValidationErrorDomain;
NS_ENUM(NSInteger) {
YTKRequestValidationErrorInvalidStatusCode = -8,
YTKRequestValidationErrorInvalidJSONFormat = -9,
};
// HTTP 请求方式
typedef NS_ENUM(NSInteger, YTKRequestMethod) {
YTKRequestMethodGET = 0,
YTKRequestMethodPOST,
YTKRequestMethodHEAD,
YTKRequestMethodPUT,
YTKRequestMethodDELETE,
YTKRequestMethodPATCH,
};
// 请求数据序列化的方式 HTTP还是JSON
typedef NS_ENUM(NSInteger, YTKRequestSerializerType) {
YTKRequestSerializerTypeHTTP = 0,
YTKRequestSerializerTypeJSON,
};
// 返回数据的序列化方式,决定了responseObject的数据类型
typedef NS_ENUM(NSInteger, YTKResponseSerializerType) {
-- NSData
YTKResponseSerializerTypeHTTP,
-- JSON 对象
YTKResponseSerializerTypeJSON,
-- NSXMLParser
YTKResponseSerializerTypeXMLParser,
};
// 请求的优先级
typedef NS_ENUM(NSInteger, YTKRequestPriority) {
YTKRequestPriorityLow = -4L,
YTKRequestPriorityDefault = 0,
YTKRequestPriorityHigh = 4,
};
// 声明了3个block
@protocol AFMultipartFormData;
typedef void (^AFConstructingBlock)(id formData);
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);
@class YTKBaseRequest;
typedef void(^YTKRequestCompletionBlock)(__kindof YTKBaseRequest *request);
// 声明了YTKRequestDelegate协议,定义了一系列可以用来接受网络相关的消息的方法,所有的代理方法将在主队列中调用
@protocol YTKRequestDelegate
@optional
// 请求成功结束
- (void)requestFinished:(__kindof YTKBaseRequest *)request;
// 请求失败
- (void)requestFailed:(__kindof YTKBaseRequest *)request;
@end
// YTKRequestAccessory协议定义了一系列用来跟踪请求状态的方法,所有的代理方法将在主队列中调用
@protocol YTKRequestAccessory
@optional
// 请求即将开始
- (void)requestWillStart:(id)request;
// 请求即将结束(这个方法将在调用requestFinished和successCompletionBlock前执行)
- (void)requestWillStop:(id)request;
// 请求已经结束(这个方法将在调用requestFinished和successCompletionBlock后执行)
- (void)requestDidStop:(id)request;
@end
// YTKBaseRequest是网络请求的抽象类,它提供了许多选项用于构建请求,是YTKRequest的基类
@interface YTKBaseRequest : NSObject
#pragma mark - Request and Response Information
///=============================================================================
/// @name Request and Response Information
///=============================================================================
// NSURLSessionTask底层相关的
// 在请求开始之前这个值是空且不应该被访问
@property (nonatomic, strong, readonly) NSURLSessionTask *requestTask;
// 就是requestTask.currentRequest
@property (nonatomic, strong, readonly) NSURLRequest *currentRequest;
// 就是requestTask.originalRequest
@property (nonatomic, strong, readonly) NSURLRequest *originalRequest;
// 就是requestTask.response
@property (nonatomic, strong, readonly) NSHTTPURLResponse *response;
/// The response status code.
@property (nonatomic, readonly) NSInteger responseStatusCode;
/// The response header fields.
@property (nonatomic, strong, readonly, nullable) NSDictionary *responseHeaders;
// 响应的数据表现形式,请求失败则是nil
@property (nonatomic, strong, readonly, nullable) NSData *responseData;
// 响应的字符串表现形式,请求失败则是nil
@property (nonatomic, strong, readonly, nullable) NSString *responseString;
/// This serialized response object. The actual type of this object is determined by
/// `YTKResponseSerializerType`. Note this value can be nil if request failed.
///
/// @discussion If `resumableDownloadPath` and DownloadTask is using, this value will
/// be the path to which file is successfully saved (NSURL), or nil if request failed.
//
@property (nonatomic, strong, readonly, nullable) id responseObject;
// 如果设置响应序列化方式是YTKResponseSerializerTypeJSON,这个就是响应结果序列化后的对象
@property (nonatomic, strong, readonly, nullable) id responseJSONObject;
// 请求序列化错误或者网络错误,默认是nil
@property (nonatomic, strong, readonly, nullable) NSError *error;
// 请求任务是否已经取消(self.requestTask.state == NSURLSessionTaskStateCanceling)
@property (nonatomic, readonly, getter=isCancelled) BOOL cancelled;
// 请求任务是否在执行(self.requestTask.state == NSURLSessionTaskStateRunning)
@property (nonatomic, readonly, getter=isExecuting) BOOL executing;
#pragma mark - Request Configuration
///=============================================================================
/// @name Request Configuration
///=============================================================================
// tag可以用来标识请求,默认是0
@property (nonatomic) NSInteger tag;
// userInfo可以用来存储请求的附加信息,默认是nil
@property (nonatomic, strong, nullable) NSDictionary *userInfo;
// 请求的代理,如果使用了block回调就可以忽略这个,默认为nil
@property (nonatomic, weak, nullable) id delegate;
// 请求成功的回调,如果block存在并且requestFinished代理方法也实现了的话,两个都会被调用,先调用代理,再在主队列中调用block
@property (nonatomic, copy, nullable) YTKRequestCompletionBlock successCompletionBlock;
// 请求失败的回调,如果block存在并且requestFailed代理方法也实现了的话,两个都会被调用,先调用代理,再在主队列中调用block
@property (nonatomic, copy, nullable) YTKRequestCompletionBlock failureCompletionBlock;
// 设置附加对象(这是什么鬼?)如果调用addAccessory来增加,这个数组会自动创建,默认是nil
@property (nonatomic, strong, nullable) NSMutableArray> *requestAccessories;
// 可以用于在POST请求中需要时构造HTTP body,默认是nil
@property (nonatomic, copy, nullable) AFConstructingBlock constructingBodyBlock;
// 设置断点续传下载请求的地址,默认是nil
// 在请求开始之前,路径上的文件将被删除。如果请求成功,文件将会自动保存到这个路径,否则响应将被保存到responseData和responseString中。为了实现这个工作,服务器必须支持Range并且响应需要支持`Last-Modified`和`Etag`,具体了解NSURLSessionDownloadTask
@property (nonatomic, strong, nullable) NSString *resumableDownloadPath;
// 捕获下载进度,也可以看看resumableDownloadPath
@property (nonatomic, copy, nullable) AFURLSessionTaskProgressBlock resumableDownloadProgressBlock;
// 设置请求优先级,在iOS8 + 可用,默认是YTKRequestPriorityDefault = 0
@property (nonatomic) YTKRequestPriority requestPriority;
// 设置请求完成回调block
- (void)setCompletionBlockWithSuccess:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure;
// 清除请求回调block
- (void)clearCompletionBlock;
// 添加遵循YTKRequestAccessory协议的请求对象,相关的requestAccessories
- (void)addAccessory:(id)accessory;
#pragma mark - Request Action
// 将当前self网络请求加入请求队列,并且开始请求
- (void)start;
// 从请求队列中移除self网络请求,并且取消请求
- (void)stop;
// 使用带有成功失败blcok回调的方法开始请求(储存block,调用start)
- (void)startWithCompletionBlockWithSuccess:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure;
#pragma mark - Subclass Override
///=============================================================================
/// @name Subclass Override
///=============================================================================
// 请求成功后,在切换到主线程之前,在后台线程上调用。要注意,如果加载了缓存,则将在主线程上调用此方法,就像`request Complete Filter`一样。
- (void)requestCompletePreprocessor;
// 请求成功时会在主线程被调用
- (void)requestCompleteFilter;
// 请求成功后,在切换到主线程之前,在后台线程上调用。
- (void)requestFailedPreprocessor;
// 请求失败时会在主线程被调用
- (void)requestFailedFilter;
// 基础URL,应该只包含地址的主要地址部分,如http://www.example.com
- (NSString *)baseUrl;
// 请求地址的URL,应该只包含地址的路径部分,如/v1/user。baseUrl和requestUrl使用[NSURL URLWithString:relativeToURL]进行连接。所以要正确返回。
// 如果requestUrl本身就是一个有效的URL,将不再和baseUrl连接,baseUrl将被忽略
- (NSString *)requestUrl;
// 可选的CDN请求地址
- (NSString *)cdnUrl;
// 设置请求超时时间,默认60秒.
// 如果使用了resumableDownloadPath(NSURLSessionDownloadTask),NSURLRequest的timeoutInterval将会被完全忽略,一个有效的设置超时时间的方法就是设置NSURLSessionConfiguration的timeoutIntervalForResource属性。
- (NSTimeInterval)requestTimeoutInterval;
// 设置请求的参数
- (nullable id)requestArgument;
// 重写这个方法可以在缓存时过滤请求中的某些参数
- (id)cacheFileNameFilterForRequestArgument:(id)argument;
// 设置 HTTP 请求方式
- (YTKRequestMethod)requestMethod;
// 设置请求数据序列化的方式
- (YTKRequestSerializerType)requestSerializerType;
// 设置请求数据序列化的方式. See also `responseObject`.
- (YTKResponseSerializerType)responseSerializerType;
// 用来HTTP授权的用户名和密码,应该返回@[@"Username", @"Password"]这种格式
- (nullable NSArray *)requestAuthorizationHeaderFieldArray;
// 附加的HTTP 请求头
- (nullable NSDictionary *)requestHeaderFieldValueDictionary;
// 用来创建完全自定义的请求,返回一个NSURLRequest,忽略`requestUrl`, `requestTimeoutInterval`,`requestArgument`, `allowsCellularAccess`, `requestMethod`,`requestSerializerType`
- (nullable NSURLRequest *)buildCustomUrlRequest;
// 发送请求时是否使用CDN
- (BOOL)useCDN;
// 是否允许请求使用蜂窝网络,默认是允许
- (BOOL)allowsCellularAccess;
// 验证 responseJSONObject 是否正确的格式化了
- (nullable id)jsonValidator;
// 验证 responseStatusCode 是否是有效的,默认是code在200-300之间是有效的
- (BOOL)statusCodeValidator;
@end
NS_ASSUME_NONNULL_END