本篇是第三篇關於網絡請求的,將講述YTKNetwork源碼,上述兩篇分別講述AFNetworking源碼解析以及結合本身項目封裝AFNetworking。css
AFNetworking源碼解析:http://www.javashuo.com/article/p-qphuutmo-bt.htmlhtml
封裝AFNetworking代碼:http://www.javashuo.com/article/p-tpkndbbq-v.html 相應的github地址:https://github.com/zxy1829760/testAFgit
YTKNetwork不只是對AFNetworking源碼的再次封裝,並且功能也增多了很多。讀完本篇大約須要20-35分鐘,建議先收藏一下。其實想對YTKNetwork進行講解好久了,因爲本人如今的項目基於YTKNetwork,抽時候給你們講解對YTKNetwork的理解。github
1、YTKNetwork思想json
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對命令模式的實現是很符合其設計標準的,它將請求的發起者和接收者分離開來(中間隔着調用者),可讓咱們隨時更換接受者。
2、YTKNetwork優點(相比AFNetworking)
相比AFNetworking而言,功能增長很多:下面將一一介紹這些功能怎麼實現的。
(1)支持統一配置服務器和CDN的地址;
(2)支持按時間緩存網絡請求內容;
(3)支持批量的網絡請求發送,並統一配置回調;
(4)支持相互依賴的網絡請求的發送;
(5)支持檢查JSON內容的合法性;
(6)支持按版本號緩存網絡請求內容;
(7)支持文件的斷點上傳;
(8)支持網絡請求的Url的filter,能夠統一爲網絡請求加上一些參數或者修改一些路徑。
3、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增長一些分類。
4、代碼解析
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仍是JSON typedef NS_ENUM(NSInteger, YTKRequestSerializerType) { YTKRequestSerializerTypeHTTP = 0, YTKRequestSerializerTypeJSON, }; // 返回數據的序列化方式,決定了responseObject的數據類型 typedef NS_ENUM(NSInteger, YTKResponseSerializerType) { -- NSData YTKResponseSerializerTypeHTTP, -- JSON 對象 YTKResponseSerializerTypeJSON, -- NSXMLParser YTKResponseSerializerTypeXMLParser, }; // 請求的優先級 typedef NS_ENUM(NSInteger, YTKRequestPriority) { YTKRequestPriorityLow = -4L, YTKRequestPriorityDefault = 0, YTKRequestPriorityHigh = 4, }; // 聲明瞭3個block @protocol AFMultipartFormData; typedef void (^AFConstructingBlock)(id<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; @end NS_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方法配置服務:
緊接着:[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. 獲取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方法,用戶也能夠根據本身定義的請求體定義參數。
以下是本身項目中重寫的
咱們拿到參數以後,而後看一下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上。好餓了,尚未吃晚餐,回去吃晚餐啦。
今天是端午,祝你們端午安康!!!