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代碼結構如下:
上面就是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_BEGINFOUNDATION_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) {-- NSDataYTKResponseSerializerTypeHTTP,-- JSON 對象YTKResponseSerializerTypeJSON,-- NSXMLParserYTKResponseSerializerTypeXMLParser, };// 請求的優先級 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)successfailure:(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)successfailure:(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 請求成功的blockif (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 triggerif (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方法配置服務:
緊接著:[self setupServer];
?在IOAApiManager實現類方法:
我們以后就可以通過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. 獲取taskNSParameterAssert(request != nil);NSError * __autoreleasing requestSerializationError = nil;//獲取用戶自定義的requestURLNSURLRequest *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 requestYTKLog(@"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. 獲得請求urlNSString *url = [self buildRequestUrl:request];//3. 獲得請求參數id param = request.requestArgument;AFConstructingBlock constructingBlock = [request constructingBodyBlock];//4. 獲得request serializerAFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];//5. 根據不同的請求類型來返回對應的taskswitch (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方法,用戶也可以根據自己定義的請求體定義參數。
如下是自己項目中重寫的
我們拿到參數之后,然后看一下dataTaskWithHTTPMethod:requestSerializer:URLString:parameters:error:方法來獲取NSURLSessionTask實例這個方法。
//YTKNetworkAgent.m - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)methodrequestSerializer:(AFHTTPRequestSerializer *)requestSerializerURLString:(NSString *)URLStringparameters:(id)parameterserror:(NSError * _Nullable __autoreleasing *)error {return [self dataTaskWithHTTPMethod:method requestSerializer:requestSerializer URLString:URLString parameters:parameters constructingBodyWithBlock:nil error:error]; }//最終返回NSURLSessionDataTask實例 - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)methodrequestSerializer:(AFHTTPRequestSerializer *)requestSerializerURLString:(NSString *)URLStringparameters:(id)parametersconstructingBodyWithBlock:(nullable void (^)(idformData))blockerror:(NSError * _Nullable __autoreleasing *)error {NSMutableURLRequest *request = nil;//根據有無構造請求體的block的情況來獲取requestif (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:requestcompletionHandler:^(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對應的responserequest.responseObject = responseObject;//3. 獲取responseObject,responseData和responseStringif ([request.responseObject isKindOfClass:[NSData class]]) {//3.1 獲取 responseDatarequest.responseData = responseObject;//3.2 獲取responseStringrequest.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不為nilsucceed = 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到cacheMetadataFilePathYTKCacheMetadata *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賦給requestrequest.responseData = [NSData dataWithContentsOfURL:url];request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];[[NSFileManager defaultManager] removeItemAtURL:url error:nil];}//清空requestrequest.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代碼,就調用blockif (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里第一個請求已經成功結束,則不能再startif (_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];}//調用批量請求成功的blockif (_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];}//調用請求失敗的blockif (_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,則取出并startif ([_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 {//獲取當前請求的indexNSUInteger 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上。好餓了,還沒有吃晚餐,回去吃晚餐啦。
今天是端午,祝大家端午安康!!!
?
轉載于:https://www.cnblogs.com/guohai-stronger/p/9194519.html
總結
以上是生活随笔為你收集整理的YTKNetwork源码详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Tool】Augmentor和imga
- 下一篇: docker系列之安装配置-2