为了账号安全,请及时绑定邮箱和手机立即绑定

YTKNetwork源码详解

标签:
深度学习

本篇是第三篇关于网络请求的,将讲述YTKNetwork源码,上述两篇分别讲述AFNetworking源码解析以及结合自己项目封装AFNetworking。

AFNetworking源码解析:https://www.cnblogs.com/guohai-stronger/p/9191497.html

封装AFNetworking代码:https://www.cnblogs.com/guohai-stronger/p/9193465.html  相应的github地址:https://github.com/zxy1829760/testAF

YTKNetwork不仅是对AFNetworking源码的再次封装,而且功能也增多了不少。读完本篇大约需要20-35分钟,建议先收藏一下。其实想对YTKNetwork进行讲解很久了,由于本人现在的项目基于YTKNetwork,抽时候给大家讲解对YTKNetwork的理解。

 

 一、YTKNetwork思想

YTK基本思想是把每一个网络请求封装成对象。把每一个网络请求封装成对象模式也就是设计模式-Command模式(Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.(将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。))

拓展>>>>

Command模式:命令模式

命令模式的本质是命令的封装,将发出命令的责任和执行命令的责任分隔开。命令模式允许请求的一方和接受的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是如何被接收,以及操作是否被执行,何时被执行,以及怎么被执行的。

举一个例子:

一个客人在点菜,把服务员喊过来,1.服务员给菜单2,你点菜,点好菜给服务员3,服务员把点菜给厨师4,厨师做好菜之后给服务员5,服务员把菜给你。

在这里,命令就好比是订单,而你是命令的发起者。你的命令通过服务员交给了执行命令的厨师,所以至于这道菜到底是谁做的,怎么做你是不需要知道的,你做的只是发出命令和接受结果。而且对于餐厅来说,厨师是可以随便换的,而你对此可以一无所知。反过来,厨师只需要好好把菜做好,至于是谁点的菜也不需要他考虑。

好处:

(1)将网络请求与具体的第三方依赖库隔离,方便以后更换底层的网络层;

(2)方便在基类中处理公共逻辑;

(3)方便在基类中处理缓存逻辑以及其他一些公共逻辑;

(4)方便做对象的持久化。

YTKNetwork对命令模式的实现是很符合其设计标准的,它将请求的发起者和接收者分离开来(中间隔着调用者),可以让我们随时更换接受者。

 

二、YTKNetwork优势(相比AFNetworking)

相比AFNetworking而言,功能增加不少:下面将一一介绍这些功能怎么实现的。

(1)支持统一配置服务器和CDN的地址;

(2)支持按时间缓存网络请求内容;

(3)支持批量的网络请求发送,并统一配置回调;

(4)支持相互依赖的网络请求的发送;

(5)支持检查JSON内容的合法性;

(6)支持按版本号缓存网络请求内容;

(7)支持文件的断点上传;

(8)支持网络请求的Url的filter,可以统一为网络请求加上一些参数或者修改一些路径。

 

三、YTKNetwork代码结构与各类功能

通过下载YTKNetwork,YTKNetwork github地址:https://github.com/yuantiku/YTKNetwork

下面是YTKNetwork代码结构如下:

https://img1.sycdn.imooc.com//5b27b831000176c512900768.jpg

上面就是YTKNetwork的代码类别,下面我们就一一说明各个类的作用。

 1.YTKBaseRequest

所有请求类的基类,持有NSURLSessionTask实例,responseData等重要数据,提供了一些需要子类实现的与网络请求相关的放阿飞,处理回调的block和代理,命令YTKNetworkAgent发起网络请求。

2.YTKRequest

YTKBaseRequest的子类。负责缓存的处理,请求前查询缓存;请求后写入缓存。

3.YTKNetworkConfig

被YTKRequest和YTKNetworkAgent访问。负责所有请求的全局配置,对于baseUrl和CDNUrl等等。

4.YTKNetworkAgent

真正发起请求的类,负责发起请求,结束请求,并持有一个字典来存储正在执行的请求。

5.YTKBatchRequest

可以发起批量请求,持有一个数组来保存所有的请求类。在请求执行后遍历这个数组发起请求,如果其中有一个请求返回失败,则认定本组请求失败。

6.YTKBatchRequestAgent

负责管理多个YTKBatchRequest实例,持有一个数组保存YTKBatchRequest。支持添加和删除YTKBatchRequest实例。

7.YTKChainRequest

可以发起链式请求,持有一个数组来保存所有的请求类。当某个请求结束后才能发起下一个请求,如果其中有一个请求返回失败,则认定本请求链失败。(链式请求:例如:发送请求 A,根据请求 A 的结果,选择性的发送请求 B 和 C,再根据 B 和 C 的结果,选择性的发送请求 D。)

8.YTKChainRequestAgent

负责管理多个YTKChainRequestAgent实例,持有一个数组来保存YTKChainRequest。支持添加和删除YTKChainRequest实例。

9.YTKNetworkPrivate

提供JSON验证,appVersion等辅助性的方法;给YTKBaseRequest增加一些分类。

 

四、代码解析

1.YTKNetwork.h

YTKNetwork通过YTKNetwork.h管理其他类别,只需要在.pch导入YTKNetwork.h即可,YTKNetwork.h代码如下:

复制代码

#import <Foundation/Foundation.h>#ifndef _YTKNETWORK_    #define _YTKNETWORK_#if __has_include(<YTKNetwork/YTKNetwork.h>)

    FOUNDATION_EXPORT double YTKNetworkVersionNumber;
    FOUNDATION_EXPORT const unsigned char YTKNetworkVersionString[];    #import <YTKNetwork/YTKRequest.h>    #import <YTKNetwork/YTKBaseRequest.h>    #import <YTKNetwork/YTKNetworkAgent.h>    #import <YTKNetwork/YTKBatchRequest.h>    #import <YTKNetwork/YTKBatchRequestAgent.h>    #import <YTKNetwork/YTKChainRequest.h>    #import <YTKNetwork/YTKChainRequestAgent.h>    #import <YTKNetwork/YTKNetworkConfig.h>#else

    #import "YTKRequest.h"
    #import "YTKBaseRequest.h"
    #import "YTKNetworkAgent.h"
    #import "YTKBatchRequest.h"
    #import "YTKBatchRequestAgent.h"
    #import "YTKChainRequest.h"
    #import "YTKChainRequestAgent.h"
    #import "YTKNetworkConfig.h"#endif /* __has_include */#endif /* _YTKNETWORK_ */

复制代码

 

2.YTKBaseRequest

所有请求类的基类,持有NSURLSessionTask实例,responseData等重要数据,提供了一些需要子类实现的与网络请求相关的放阿飞,处理回调的block和代理,命令YTKNetworkAgent发起网络请求。

复制代码

#import <Foundation/Foundation.h>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还是JSONtypedef 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<AFMultipartFormData> formData);
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);@class YTKBaseRequest;

typedef void(^YTKRequestCompletionBlock)(__kindof YTKBaseRequest *request);// 声明了YTKRequestDelegate协议,定义了一系列可以用来接受网络相关的消息的方法,所有的代理方法将在主队列中调用@protocol YTKRequestDelegate <NSObject>@optional// 请求成功结束- (void)requestFinished:(__kindof YTKBaseRequest *)request;// 请求失败- (void)requestFailed:(__kindof YTKBaseRequest *)request;@end// YTKRequestAccessory协议定义了一系列用来跟踪请求状态的方法,所有的代理方法将在主队列中调用@protocol YTKRequestAccessory <NSObject>@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<YTKRequestDelegate> 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<id<YTKRequestAccessory>> *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<YTKRequestAccessory>)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<NSString *> *)requestAuthorizationHeaderFieldArray;// 附加的HTTP 请求头- (nullable NSDictionary<NSString *, NSString *> *)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;@endNS_ASSUME_NONNULL_END

复制代码

在其实现类:YTKBaseRequest.m没有过多的知识,仅仅对各个方法的简单实现,大家看上面方法的作用就可以啦。(如果有时间看一下YTKBaseRequest.m也是可以的)。

 

3.YTKRequest

YTKBaseRequest的子类。负责缓存的处理,请求前查询缓存;请求后写入缓存。

复制代码

@interface YTKRequest : YTKBaseRequest 
//表示当前请求,是否忽略本地缓存responseData@property (nonatomic) BOOL ignoreCache; 
/// 返回当前缓存的对象- (id)cacheJson; 
/// 是否当前的数据从缓存获得- (BOOL)isDataFromCache; 
/// 返回是否当前缓存需要更新【缓存是否超时】- (BOOL)isCacheVersionExpired; 
/// 强制更新缓存【不使用缓存数据】- (void)startWithoutCache; 
/// 手动将其他请求的JsonResponse写入该请求的缓存- (void)saveJsonResponseToCacheFile:(id)jsonResponse; 
/// 子类重写方法【参数方法】- (NSInteger)cacheTimeInSeconds;    //当前请求指定时间内,使用缓存数据- (long long)cacheVersion;    //当前请求,指定使用版本号的缓存数据- (id)cacheSensitiveData;   
 
@end

复制代码

发现YTKRequest主要是对请求数据缓存方面的处理。再看.m实现文件:

复制代码

//YTKRequest.m- (void)start { 
    //1. 如果忽略缓存 -> 请求
    if (self.ignoreCache) {
        [self startWithoutCache];        return;
    } 
    //2. 如果存在下载未完成的文件 -> 请求
    if (self.resumableDownloadPath) {
        [self startWithoutCache];        return;
    } 
    //3. 获取缓存失败 -> 请求
    if (![self loadCacheWithError:nil]) {
        [self startWithoutCache];        return;
    } 
    //4. 到这里,说明一定能拿到可用的缓存,可以直接回调了(因为一定能拿到可用的缓存,所以一定是调用成功的block和代理)
    _dataFromCache = YES;
 
    dispatch_async(dispatch_get_main_queue(), ^{ 
        //5. 回调之前的操作        //5.1 缓存处理        [self requestCompletePreprocessor]; 
        //5.2 用户可以在这里进行真正回调前的操作        [self requestCompleteFilter];
 
        YTKRequest *strongSelf = self; 
        //6. 执行回调        //6.1 请求完成的代理
        [strongSelf.delegate requestFinished:strongSelf]; 
        //6.2 请求成功的block
        if (strongSelf.successCompletionBlock) {
            strongSelf.successCompletionBlock(strongSelf);
        } 
        //7. 把成功和失败的block都设置为nil,避免循环引用        [strongSelf clearCompletionBlock];
    });
}

复制代码

通过start()方法可以看出,它做的是请求之前的查询和检查工作。下面是具体实现的过程:

(1).ignoreCache属性是用户手动设置的,如果用户强制忽略缓存,则无论是否缓存是否存在,直接发送请求。

(2)resumableDownloadPath是断点下载路径,如果该路径不为空,说明有未完成的下载任务,则直接发送请求继续下载。

(3)loadCacheWithError:方法验证了加载缓存是否成功的方法(方法如果返回YES,说明可以加载缓存,反正则不可以加载缓存)下面是loadCacheWithError的具体实现:

复制代码

- (BOOL)loadCacheWithError:(NSError * _Nullable __autoreleasing *)error { 
    // 缓存时间小于0,则返回(缓存时间默认为-1,需要用户手动设置,单位是秒)
    if ([self cacheTimeInSeconds] < 0) {        if (error) {            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheTime userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache time"}];
        }        return NO;
    } 
    // 是否有缓存的元数据,如果没有,返回错误(元数据是指数据的数据,在这里描述了缓存数据本身的一些特征:包括版本号,缓存时间,敏感信息等等)
    if (![self loadCacheMetadata]) {        if (error) {            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidMetadata userInfo:@{ NSLocalizedDescriptionKey:@"Invalid metadata. Cache may not exist"}];
        }        return NO;
    } 
    // 有缓存,再验证是否有效
    if (![self validateCacheWithError:error]) {        return NO;
    } 
    // 有缓存,而且有效,再验证是否能取出来
    if (![self loadCacheData]) {        if (error) {            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheData userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache data"}];
        }        return NO;
    }    return YES;
}

复制代码

上面代码标红的提到缓存的元数据的,.m也实现类元数据的获取方法:loadCacheMetadata

复制代码

//YTKRequest.m- (BOOL)loadCacheMetadata {
 
    NSString *path = [self cacheMetadataFilePath];
    NSFileManager * fileManager = [NSFileManager defaultManager];    if ([fileManager fileExistsAtPath:path isDirectory:nil]) {        @try {            //将序列化之后被保存在磁盘里的文件反序列化到当前对象的属性cacheMetadata
            _cacheMetadata = [NSKeyedUnarchiver unarchiveObjectWithFile:path];            return YES;
        } @catch (NSException *exception) {
            YTKLog(@"Load cache metadata failed, reason = %@", exception.reason);            return NO;
        }
    }    return NO;
}
cacheMetadata(YTKCacheMetadata) 是当前reqeust类用来保存缓存元数据的属性。
YTKCacheMetadata类被定义在YTKRequest.m文件里面: 
//YTKRequest.m@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; 
@end//通过归档方式进行元数据的存储- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:@(self.version) forKey:NSStringFromSelector(@selector(version))];
    [aCoder encodeObject:self.sensitiveDataString forKey:NSStringFromSelector(@selector(sensitiveDataString))];
    [aCoder encodeObject:@(self.stringEncoding) forKey:NSStringFromSelector(@selector(stringEncoding))];
    [aCoder encodeObject:self.creationDate forKey:NSStringFromSelector(@selector(creationDate))];
    [aCoder encodeObject:self.appVersionString forKey:NSStringFromSelector(@selector(appVersionString))];
}- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];    if (!self) {        return nil;
    }

    self.version = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(version))] integerValue];
    self.sensitiveDataString = [aDecoder decodeObjectOfClass:[NSString class] forKey:NSStringFromSelector(@selector(sensitiveDataString))];
    self.stringEncoding = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(stringEncoding))] integerValue];
    self.creationDate = [aDecoder decodeObjectOfClass:[NSDate class] forKey:NSStringFromSelector(@selector(creationDate))];
    self.appVersionString = [aDecoder decodeObjectOfClass:[NSString class] forKey:NSStringFromSelector(@selector(appVersionString))];    return self;
}

复制代码

loadCacheMetadata方法的目的是将之前被序列化保存的缓存元数据信息反序列化,赋给自身的cacheMetadata属性上。

现在获取了缓存的元数据并赋值给cacheMetadata属性上,接下来要元数据的各项信息是否符合要求:使用validateCacheWithError:方法进行验证。

复制代码

- (BOOL)validateCacheWithError:(NSError * _Nullable __autoreleasing *)error { 
    // 是否大于过期时间
    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;
    } 
    // 缓存的版本号是否符合
    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;
    } 
    // 敏感信息是否符合
    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的版本是否符合
    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;
}

复制代码

如果每一项元数据信息都能通过,再在loadCacheData方法里面验证缓存是否能取出:

复制代码

- (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;
}

复制代码

如果通过了最终的考验,则说明当前请求对应的缓存是符合各项要求并可以被成功取出,也就是可以直接进行回调了。当确认缓存可以成功取出后,手动设置dataFromCache属性为 YES,说明当前的请求结果是来自于缓存,而没有通过网络请求。

在这里面还有一个比较重要的方法:requestCompletePreprocessor

复制代码

//YTKRequest.m:- (void)requestCompletePreprocessor {
 
    [super requestCompletePreprocessor]; 
    //是否异步将responseData写入缓存(写入缓存的任务放在专门的队列ytkrequest_cache_writing_queue进行)
    if (self.writeCacheAsynchronously) {
 
        dispatch_async(ytkrequest_cache_writing_queue(), ^{            //保存响应数据到缓存            [self saveResponseDataToCacheFile:[super responseData]];
        });
 
    } else {        //保存响应数据到缓存        [self saveResponseDataToCacheFile:[super responseData]];
    }
}//YTKRequest.m://保存响应数据到缓存- (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方法的任务是将响应数据保存起来,也就是做缓存。但是,缓存的保存有两个条件,一个是需要cacheTimeInSeconds方法返回正整数(缓存时间,单位是秒);另一个条件就是isDataFromCache方法返回NO。

进一下研究:startWithoutCache

这个方法做了哪些?

复制代码

//YTKRequest.m- (void)startWithoutCache { 
    //1. 清除缓存    [self clearCacheVariables]; 
    //2. 调用父类的发起请求    [super start];
}//YTKBaseRequest.m:- (void)start { 
    //1. 告诉Accessories即将回调了(其实是即将发起请求)    [self toggleAccessoriesWillStartCallBack]; 
    //2. 令agent添加请求并发起请求,在这里并不是组合关系,agent只是一个单例    [[YTKNetworkAgent sharedAgent] addRequest:self];
}

复制代码

 

4.YTKNetworkConfig

被YTKRequest和YTKNetworkAgent访问。负责所有请求的全局配置,对于baseUrl和CDNUrl等等。

在实际业务中,作为公司的测试需要不断的切换服务器地址,确定数据的正确性,也间接说明YTKNetworkConfig的必要性。

下面是以自己目前公司所用的YTKNetwork的YTKNetworkConfig的用处:

(1)首先在AppDelegate中:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法配置服务:

https://img1.sycdn.imooc.com//5b27b8590001d51610680612.jpg

紧接着:[self setupServer];

https://img1.sycdn.imooc.com//5b27b88b00017f2110800300.jpg

 在IOAApiManager实现类方法:

https://img1.sycdn.imooc.com//5b27b89b0001facb12341398.jpg

我们以后就可以通过baseUrl进行不同的地址访问啦。大部分企业可能需要一些静态资源(例如图片,js,css等)这就使用到了CDN,YTKNetworkConfig的cdnUrl参数用于统一设置这一部分网络请求的地址。

上面的代码以及项目中如何对YTKNetwork再次封装的,会在下一篇博客中给出,并会上传至github。

YTKNetworkConfig源码也提供了安全策略,url过滤,缓存路径的过滤等方法如下:

复制代码

// 使用类方法创建单例对象+ (YTKNetworkConfig *)sharedConfig;// 请求的根URL,默认是空字符串@property (nonatomic, strong) NSString *baseUrl;// 请求CDN URL,默认是空字符串@property (nonatomic, strong) NSString *cdnUrl;// URL过滤池(YTKUrlFilterProtocol协议使用)@property (nonatomic, strong, readonly) NSArray<id<YTKUrlFilterProtocol>> *urlFilters;// 缓存路径的过滤池(YTKCacheDirPathFilterProtocol协议使用)@property (nonatomic, strong, readonly) NSArray<id<YTKCacheDirPathFilterProtocol>> *cacheDirPathFilters;// 同AFNetworking中使用的安全策略@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;// 是否记录调试信息,默认是NO@property (nonatomic) BOOL debugLogEnabled;// 用来初始化AFHTTPSessionManager,默认是nil@property (nonatomic, strong) NSURLSessionConfiguration* sessionConfiguration;// 添加一个新的URL过滤器- (void)addUrlFilter:(id<YTKUrlFilterProtocol>)filter;// 删除所有的URL过滤器- (void)clearUrlFilter;// 添加一个新的缓存地址过滤器- (void)addCacheDirPathFilter:(id<YTKCacheDirPathFilterProtocol>)filter;
//删除所有的缓存地址过滤器
 - (void)clearCacheDirPathFilter; @end

复制代码

 

5.YTKNetworkAgent

真正发起请求的类,负责发起请求,结束请求,并持有一个字典来存储正在执行的请求。

(1)首先从请求开始:YTKNetworkAgent把当前的请求对象添加到了自己身上并发送请求

复制代码

//YTKNetworkAgent.m- (void)addRequest:(YTKBaseRequest *)request { 
    //1. 获取task
    NSParameterAssert(request != nil);
 
    NSError * __autoreleasing requestSerializationError = nil; 
    //获取用户自定义的requestURL
    NSURLRequest *customUrlRequest= [request buildCustomUrlRequest]; 
    if (customUrlRequest) {
 
        __block NSURLSessionDataTask *dataTask = nil;        //如果存在用户自定义request,则直接走AFNetworking的dataTaskWithRequest:方法
        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 { 
        //如果用户没有自定义url,则直接走这里
        request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
 
    } 
    //序列化失败,则认定为请求失败
    if (requestSerializationError) {        //请求失败的处理        [self requestDidFailWithRequest:request error:requestSerializationError];        return;
    }
 
    NSAssert(request.requestTask != nil, @"requestTask should not be nil"); 
    // 优先级的映射    // !!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])); 
    //2. 将request放入保存请求的字典中,taskIdentifier为key,request为值    [self addRequestToRecord:request]; 
    //3. 开始task    [request.requestTask resume];
}

复制代码

这个方法可以看出调用了AFNetworking的请求方法,也验证了YTKNetwork对AFNetworking高度封装。这个方法可以分成三部分:

1>获取当前请求对应的task并赋值给request的requestTask属性

2>将request放入专门用来保存请求的字典中,key为taskIdentifier

3>启动task

下面从1>开始说起

复制代码

//YTKNetworkAgent.m- (void)addRequest:(YTKBaseRequest *)request {
 
  ... 
  if (customUrlRequest) {
 
        __block NSURLSessionDataTask *dataTask = nil;        //如果存在用户自定义request,则直接走AFNetworking的dataTaskWithRequest:方法
        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 { 
        //如果用户没有自定义url,则直接走这里
        request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
 
    }
 
  ...
}

复制代码

这里判断了用户是否自定义了request:如果用户自定义了request,则直接调用AFNetworking的dataTaskWithRequest:方法;反之,则调用YTKRequest自己生产的task方法。AFNetworking的方法这里暫不做讲解,在这讲述YTKRequest自己生产的task方法:下面是其是实现方式:

复制代码

//YTKNetworkAgent.m//根据不同请求类型,序列化类型,和请求参数来返回NSURLSessionTask- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error { 
    //1. 获得请求类型(GET,POST等)
    YTKRequestMethod method = [request requestMethod]; 
    //2. 获得请求url
    NSString *url = [self buildRequestUrl:request]; 
    //3. 获得请求参数
    id param = request.requestArgument;
    AFConstructingBlock constructingBlock = [request constructingBodyBlock]; 
    //4. 获得request serializer
    AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request]; 
    //5. 根据不同的请求类型来返回对应的task
    switch (method) { 
        case YTKRequestMethodGET: 
            if (request.resumableDownloadPath) {                //下载任务
                return [self downloadTaskWithDownloadPath:request.resumableDownloadPath requestSerializer:requestSerializer URLString:url parameters:param progress:request.resumableDownloadProgressBlock error:error];
 
            } else {                //普通get请求
                return [self dataTaskWithHTTPMethod:@"GET" requestSerializer:requestSerializer URLString:url parameters:param error:error];
            } 
        case YTKRequestMethodPOST:            //POST请求
            return [self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameters:param constructingBodyWithBlock:constructingBlock error:error]; 
        case YTKRequestMethodHEAD:            //HEAD请求
            return [self dataTaskWithHTTPMethod:@"HEAD" requestSerializer:requestSerializer URLString:url parameters:param error:error]; 
        case YTKRequestMethodPUT:            //PUT请求
            return [self dataTaskWithHTTPMethod:@"PUT" requestSerializer:requestSerializer URLString:url parameters:param error:error]; 
        case YTKRequestMethodDELETE:            //DELETE请求
            return [self dataTaskWithHTTPMethod:@"DELETE" requestSerializer:requestSerializer URLString:url parameters:param error:error]; 
        case YTKRequestMethodPATCH:            //PATCH请求
            return [self dataTaskWithHTTPMethod:@"PATCH" requestSerializer:requestSerializer URLString:url parameters:param error:error];
    }
}

复制代码

下面再逐渐讲解这个私有方法需要的每个参数的获取方法:

1>.1.获得请求类型(GET,POST等):

复制代码

//YTKNetworkAgent.m,requestMethod方法最初在YTKBaseRequest里面已经实现了,默认返回了YTKRequestMethodGET。- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
 
  ...
  YTKRequestMethod method = [request requestMethod];
  ...
 
}

复制代码

用户可以根据实际的需求在自定义request类里面重写这个方法:

- (YTKRequestMethod)requestMethod {    return YTKRequestMethodPOST;
}

1>.2获取请求参数

复制代码

//YTKNetworkAgent.m- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
 
   ...      //获取用户提供的请求参数
    id param = request.requestArgument; 
    //获取用户提供的构造请求体的block(默认是没有的)
    AFConstructingBlock constructingBlock = [request constructingBodyBlock];
 
   ...
 
}

复制代码

requestArgument是一个get方法,用户也可以根据自己定义的请求体定义参数。

如下是自己项目中重写的

https://img1.sycdn.imooc.com//5b27b8ab0001befa13540460.jpg

我们拿到参数之后,然后看一下dataTaskWithHTTPMethod:requestSerializer:URLString:parameters:error:方法来获取NSURLSessionTask实例这个方法。

复制代码

//YTKNetworkAgent.m- (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实例- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                               requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                       constructingBodyWithBlock:(nullable void (^)(idformData))block
                                           error:(NSError * _Nullable __autoreleasing *)error {
    NSMutableURLRequest *request = nil; 
    //根据有无构造请求体的block的情况来获取request
    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];
    } 
    //获得request以后来获取dataTask
    __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;
}

复制代码

上面的两个方法就是获取NSURLSessionDataTask实例方法。继续往下走,到了优先级的映射部分

复制代码

// 优先级的映射    // !!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;
        }
    }

复制代码

到现在我们拿到了task的实例并设置好了优先级,下面是addRequest方法的第二个部分啦!

2>YTKNetworkAgent将request实例放在了一个字典中,保存起来。

将request放入专门用来保存请求的字典中,key为taskIdentifier。

复制代码

//YTKNetworkAgent.m- (void)addRequest:(YTKBaseRequest *)request { 
  //将request实例放入保存请求的字典中,taskIdentifier为key,request为值  [self addRequestToRecord:request];
 
   ...
 
}- (void)addRequestToRecord:(YTKBaseRequest *)request { 
    //加锁    Lock();
    _requestsRecord[@(request.requestTask.taskIdentifier)] = request;
    Unlock();
}

复制代码

添加前和添加后是进行了加锁和解锁的处理的。而且request实例被保存的时候,将其task的identifier作为key来保存。

3>启动task

复制代码

- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
 
   ...
 
   [request.requestTask resume];
 
   ...
 
}

复制代码

(2)对回调的处理

复制代码

//YTKNetworkAgent.m//统一处理请求结果,包括成功和失败的情况- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error { 
    //1. 获取task对应的request    Lock();
    YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
    Unlock(); 
    //如果不存在对应的request,则立即返回
    if (!request) {        return;
    }
 
    。。。 
    //2. 获取request对应的response
    request.responseObject = responseObject; 
    //3. 获取responseObject,responseData和responseString
    if ([request.responseObject isKindOfClass:[NSData class]]) { 
       //3.1 获取 responseData
        request.responseData = responseObject; 
        //3.2 获取responseString
        request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]]; 
         //3.3 获取responseObject(或responseJSONObject)        //根据返回的响应的序列化的类型来得到对应类型的响应
        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;
        }
    } 
    //4. 判断是否有错误,将错误对象赋值给requestError,改变succeed的布尔值。目的是根据succeed的值来判断到底是进行成功的回调还是失败的回调
    if (error) {        //如果该方法传入的error不为nil
        succeed = NO;
        requestError = error;
 
    } else if (serializationError) {        //如果序列化失败了
        succeed = NO;
        requestError = serializationError;
 
    } else { 
        //即使没有error而且序列化通过,也要验证request是否有效
        succeed = [self validateResult:request error:&validationError];
        requestError = validationError;
    } 
    //5. 根据succeed的布尔值来调用相应的处理
    if (succeed) {        //请求成功的处理        [self requestDidSucceedWithRequest:request];
    } else { 
        //请求失败的处理        [self requestDidFailWithRequest:request error:requestError];
    } 
     //6. 回调完成的处理
    dispatch_async(dispatch_get_main_queue(), ^{        //6.1 在字典里移除当前request        [self removeRequestFromRecord:request];         //6.2 清除所有block        [request clearCompletionBlock];
    });
}

复制代码

我们可以和上面一样,对于上面代码做剖析处理

1>首先是通过task的identifier值从YTKNetworkAgent保存的字典里获取对应的请求;

2>然后将获得的responseObject进行处理,将处理后获得的responseObject,responseData和  responseString赋值给当前的请求实例request;

3>根据获取情况判断succes值,然后根据success值进行成功和失败的回调。

(1.1)验证完返回的JSON数据是否有效以后,就可以进行回调啦!成功的回调如下:

复制代码

//YTKNetworkAgent.m//请求成功:主要负责将结果写入缓存&回调成功的代理和block- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
 
    @autoreleasepool {        //写入缓存         [request requestCompletePreprocessor];
    }
 
    dispatch_async(dispatch_get_main_queue(), ^{ 
        //告诉Accessories请求就要停止了        [request toggleAccessoriesWillStopCallBack]; 
        //在真正的回调之前做的处理,用户自定义        [request requestCompleteFilter]; 
        //如果有代理,则调用成功的代理
        if (request.delegate != nil) {
            [request.delegate requestFinished:request];
        } 
        //如果传入了成功回调的代码,则调用
        if (request.successCompletionBlock) {
            request.successCompletionBlock(request);
        } 
        //告诉Accessories请求已经结束了        [request toggleAccessoriesDidStopCallBack];
    });
}

复制代码

我们可以看到请求成功之后,第一件事是写入缓存。requestCompletePreprocessor该方法在YTKRequest.m里有实现。

复制代码

//YTKRequest.m- (void)requestCompletePreprocessor {
 
    [super requestCompletePreprocessor]; 
    //是否异步将responseData写入缓存(写入缓存的任务放在专门的队列进行)
    if (self.writeCacheAsynchronously) {
 
        dispatch_async(ytkrequest_cache_writing_queue(), ^{            //写入缓存文件            [self saveResponseDataToCacheFile:[super responseData]];
        });
 
    } else {         //写入缓存文件        [self saveResponseDataToCacheFile:[super responseData]];
    }
} 
//写入缓存文件- (void)saveResponseDataToCacheFile:(NSData *)data { //写入缓存操作的执行条件:当cacheTimeInSeconds方法返回大于0并且isDataFromCache为NO的时候会进行写入缓存。
//cacheTimeInSeconds方法返回的是缓存保存的时间,它最初定义在YTKBaseRquest里面,默认返回是-1,YTKNetwork默认是不进行缓存的,如果用户需要做缓存,则需要在自定义的request类里面返回一个大于0的整数,这个整数的单位是秒
    if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {        if (data != nil) {            @try {                // 1. 保存request的responseData到cacheFilePath                [data writeToFile:[self cacheFilePath] atomically:YES]; 
                // 2. 保存request的metadata到cacheMetadataFilePath
                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保存的有两种形式:第一种是纯粹的NSData类型的实例。第二种是描述当前NSData实例的元数据YTKCacheMetadata的实例。

(1.2)上面是成功的回调,看一下失败的回调:

复制代码

//YTKNetworkAgent.m//请求失败- (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); 
    // 储存未完成的下载数据
    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.    //如果下载任务失败,则取出对应的响应文件并清空
    if ([request.responseObject isKindOfClass:[NSURL class]]) {
        NSURL *url = request.responseObject; 
        //isFileURL:是否是文件,如果是,则可以再isFileURL获取;&&后面是再次确认是否存在改url对应的文件
        if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) { 
            //将url的data和string赋给request
            request.responseData = [NSData dataWithContentsOfURL:url];
            request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
 
            [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
        } 
        //清空request
        request.responseObject = nil;
    }
 
 
    @autoreleasepool {        //请求失败的预处理,YTK没有定义,需要用户定义        [request requestFailedPreprocessor];
    }
 
    dispatch_async(dispatch_get_main_queue(), ^{ 
        //告诉Accessories请求就要停止了        [request toggleAccessoriesWillStopCallBack]; 
        //在真正的回调之前做的处理        [request requestFailedFilter]; 
        //如果有代理,就调用代理
        if (request.delegate != nil) {
            [request.delegate requestFailed:request];
        } 
        //如果传入了失败回调的block代码,就调用block
        if (request.failureCompletionBlock) {
            request.failureCompletionBlock(request);
        } 
        //告诉Accessories请求已经停止了        [request toggleAccessoriesDidStopCallBack];
    });
}

复制代码

通过上面方法可看出:首先判断了当前任务是不是下载任务,如果是,储存当前已经下载好的data到resumableDownloadPath里面。而如果下载任务失败,则将其对应的在本地保存的路径上的文件清空。

以上就是YTKNetworkAgent类的大概内容,也是YTKNetwork单个请求的流程。

 

6.YTKBatchRequest

可以发起批量请求,持有一个数组来保存所有的请求类。在请求执行后遍历这个数组发起请求,如果其中有一个请求返回失败,则认定本组请求失败。

下面看一个初始化方法

复制代码

//YTKBatchRequest.m- (instancetype)initWithRequestArray:(NSArray*)requestArray {
    self = [super init];    if (self) { 
        //保存为属性
        _requestArray = [requestArray copy]; 
        //批量请求完成的数量初始化为0
        _finishedCount = 0; 
        //类型检查,所有元素都必须为YTKRequest或的它的子类,否则强制初始化失败
        for (YTKRequest * req in _requestArray) {            if (![req isKindOfClass:[YTKRequest class]]) {
                YTKLog(@"Error, request item must be YTKRequest instance.");                return nil;
            }
        }
    }    return self;
}

复制代码

初始化以后,我们就可以调用start方法来发起当前YTKBatchRequest实例所管理的所有请求了。

复制代码

- (void)start { 
    //如果batch里第一个请求已经成功结束,则不能再start
    if (_finishedCount > 0) {
        YTKLog(@"Error! Batch request has already started.");        return;
    } 
    //最开始设定失败的request为nil
    _failedRequest = nil; 
    //使用YTKBatchRequestAgent来管理当前的批量请求    [[YTKBatchRequestAgent sharedAgent] addBatchRequest:self];
    [self toggleAccessoriesWillStartCallBack]; 
    //遍历所有request,并开始请求
    for (YTKRequest * req in _requestArray) {
        req.delegate = self;
        [req clearCompletionBlock];
        [req start];
    }
}

复制代码

所以在这里是遍历YTKBatchRequest实例的_requestArray并逐一发送请求。因为已经封装好了单个的请求,所以在这里直接start就好了。

(1)在请求之后,在每一个请求的回调的代理方法里面,来判断这次请求是否是成功的,也是在YTKRequest子类YTKBatchRequest.m判断。

复制代码

//YTKBatchRequest.m#pragma mark - Network Request Delegate
- (void)requestFinished:(YTKRequest *)request { 
    //某个request成功后,首先让_finishedCount + 1
    _finishedCount++; 
    //如果_finishedCount等于_requestArray的个数,则判定当前batch请求成功
    if (_finishedCount == _requestArray.count) { 
        //调用即将结束的代理        [self toggleAccessoriesWillStopCallBack]; 
        //调用请求成功的代理
        if ([_delegate respondsToSelector:@selector(batchRequestFinished:)]) {
            [_delegate batchRequestFinished:self];
        } 
        //调用批量请求成功的block
        if (_successCompletionBlock) {
            _successCompletionBlock(self);
        } 
        //清空成功和失败的block        [self clearCompletionBlock]; 
        //调用请求结束的代理        [self toggleAccessoriesDidStopCallBack]; 
        //从YTKBatchRequestAgent里移除当前的batch        [[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
    }
}

复制代码

在某个请求的回调成功以后,会让成功计数+1。在+1以后,如果成功计数和当前批量请求数组里元素的个数相等,则判定当前批量请求成功,并进行当前批量请求的成功回调。

(2)失败回调

复制代码

//YTKBatchRequest.m- (void)requestFailed:(YTKRequest *)request {
 
    _failedRequest = request; 
    //调用即将结束的代理    [self toggleAccessoriesWillStopCallBack]; 
    //停止batch里所有的请求
    for (YTKRequest *req in _requestArray) {
        [req stop];
    } 
    //调用请求失败的代理
    if ([_delegate respondsToSelector:@selector(batchRequestFailed:)]) {
        [_delegate batchRequestFailed:self];
    } 
    //调用请求失败的block
    if (_failureCompletionBlock) {
        _failureCompletionBlock(self);
    } 
    //清空成功和失败的block    [self clearCompletionBlock]; 
    //调用请求结束的代理    [self toggleAccessoriesDidStopCallBack]; 
    //从YTKBatchRequestAgent里移除当前的batch    [[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}

复制代码

从上面代码可以看出,如果批量请求里面有一个request失败了,则判定当前批量请求失败。

 

7.YTKBatchRequestAgent

负责管理多个YTKBatchRequest实例,持有一个数组保存YTKBatchRequest。支持添加和删除YTKBatchRequest实例。

 YTKBatchRequestAgent和YTKRequestAgent差不多,只不过是一个和多个区别,没有多少要核心讲的,可以自己看一下YTKBatchRequestAgent的源码,相信都能看懂。

 

8.YTKChainRequest

可以发起链式请求,持有一个数组来保存所有的请求类。当某个请求结束后才能发起下一个请求,如果其中有一个请求返回失败,则认定本请求链失败。(链式请求:例如:发送请求 A,根据请求 A 的结果,选择性的发送请求 B 和 C,再根据 B 和 C 的结果,选择性的发送请求 D。)

 处理链式请求的类是YTKChainRequest,利用YTKChainRequestAgent单例来管理YTKChainRequest实例。

初始化方法

复制代码

//YTKChainRequest.m- (instancetype)init {
 
    self = [super init];    if (self) { 
        //下一个请求的index
        _nextRequestIndex = 0; 
        //保存链式请求的数组
        _requestArray = [NSMutableArray array]; 
        //保存回调的数组
        _requestCallbackArray = [NSMutableArray array]; 
        //空回调,用来填充用户没有定义的回调block
        _emptyCallback = ^(YTKChainRequest *chainRequest, YTKBaseRequest *baseRequest) {            // do nothing        };
    }    return self;
}

复制代码

YTKChainRequest提供了添加和删除request的接口。

复制代码

//在当前chain添加request和callback- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback { 
    //保存当前请求    [_requestArray addObject:request]; 
    if (callback != nil) {
        [_requestCallbackArray addObject:callback];
    } else {        //之所以特意弄一个空的callback,是为了避免在用户没有给当前request的callback传值的情况下,造成request数组和callback数组的不对称        [_requestCallbackArray addObject:_emptyCallback];
    }
}

复制代码

(1)链式请求发起

复制代码

//YTKChainRequest.m- (void)start {    //如果第1个请求已经结束,就不再重复start了
    if (_nextRequestIndex > 0) {
        YTKLog(@"Error! Chain request has already started.");        return;
    }    //如果请求队列数组里面还有request,则取出并start
    if ([_requestArray count] > 0) {
        [self toggleAccessoriesWillStartCallBack];        //取出当前request并start        [self startNextRequest];        //在当前的_requestArray添加当前的chain(YTKChainRequestAgent允许有多个chain)        [[YTKChainRequestAgent sharedAgent] addChainRequest:self];
    } else {
        YTKLog(@"Error! Chain request array is empty.");
    }
}

复制代码

通过查看链式请求的实现,发现链式请求的请求队列是可以变动的,用户可以无限制地添加请求。只要请求队列里面有请求存在,则YTKChainRequest就会继续发送它们。

(2)链式请求的请求和回调

下面是终止方法stop()

复制代码

//YTKChainRequest.m//终止当前的chain- (void)stop { 
    //首先调用即将停止的callback    [self toggleAccessoriesWillStopCallBack]; 
    //然后stop当前的请求,再清空chain里所有的请求和回掉block    [self clearRequest]; 
    //在YTKChainRequestAgent里移除当前的chain    [[YTKChainRequestAgent sharedAgent] removeChainRequest:self]; 
    //最后调用已经结束的callback    [self toggleAccessoriesDidStopCallBack];
}

复制代码

stop方法是可以在外部调用的,所以用户可以随时终止当前链式请求的进行。它首先调用clearReuqest方法,将当前request停止,再将请求队列数组和callback数组清空。

复制代码

//YTKChainRequest.m- (void)clearRequest {    //获取当前请求的index
    NSUInteger currentRequestIndex = _nextRequestIndex - 1;    if (currentRequestIndex < [_requestArray count]) {
        YTKBaseRequest *request = _requestArray[currentRequestIndex];
        [request stop];
    }
    [_requestArray removeAllObjects];
    [_requestCallbackArray removeAllObjects];
}

复制代码

然后在YTKChainRequestAgent单例里面,将自己移除掉。

 

9.YTKNetworkPrivate

提供JSON验证,appVersion等辅助性的方法;给YTKBaseRequest增加一些分类。

如果你通读了上述文章,你会发现在各个类里面可能会用到的一些工具类方法,在YTKNetworkPrivate都会给出,进一步提高了封装性和代码的可移植性。

 

上述就是YTKNetwork源码的全部内容,希望对想要了解YTKNetwork内容的工程师有所帮助。今天又是端午的最后一天,自己花了11个小时写了这篇文章,也是答应说在端午前后对于网络请求的第三次讲解。

明天就上班啦,自己也会抽出时间写对YTKNetwork再次针对实际过的项目进行封装,也会及时上传到github上。好饿了,还没有吃晚餐,回去吃晚餐啦。

今天是端午,祝大家端午安康!!!

原文出处


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消