YTKNetwork源碼詳解

本篇是第三篇關於網絡請求的,將講述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上。好餓了,尚未吃晚餐,回去吃晚餐啦。

今天是端午,祝你們端午安康!!!

相關文章
相關標籤/搜索