首先
關於網路層最先可能想到的是AFNetworking
,或者Swift中的Alamofire
,直接使用起來也特別的簡單,但是稍複雜的專案如果直接使用就顯得不夠用了,首先第三方耦合不說,就光散落在各處的請求回撥就難以後期維護,所以一般會有針對性的再次封裝,往往初期可能業務相對簡單,考慮的方面較少,後期業務增加可能需要對網路層進行重構,一個好的架構也一定是和業務層面緊密相連的,隨業務的增長不斷健壯的。
最近也是看了YTKNetwork的原始碼和相關部落格,站在前輩的肩膀上寫下一些自己關於網路層的解讀。
與業務層對接方式
常見的與業務層對接方式兩種:
- 集約型:
最典型就屬於上面說的AFNetworking
、Alamofire
,發起網路請求都集中在一個類上,請求回撥通過Block、閉包實現的,Block、閉包回撥有比較好的靈活性,可以方便的在任何位置發起請求,同時也可能是不好的地方,網路請求回撥散落在各處,不便於維護。
下面是一個集約型的網路請求,大家使用集約型網路請求有沒有遇到這麼一個場景,請求回撥後需要做比較多的處理,程式碼量多的時候,會再定義一個私有方法把程式碼寫在裡面,在Block中呼叫在私有方法。其實這樣處理本質上和通過代理回撥本質上是一樣的。
1 2 3 4 5 |
[_manager GET:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) { //The data processing, Rendering interface } failure:^(AFHTTPRequestOperation *operation, NSError *error) { }]; |
- 離散型:
對應的是接下來的YTKNetwork,離散型最大的特點就是一個網路請求對應一個單獨的類,在這個類內部封裝請求地址、方式、引數、校驗和處理請求回來的資料,通常代理回撥,需要跨層資料傳遞時也使用通知回撥,比較集中,因為資料處理都放在內部處理了,返回資料的形式(模型化後的資料還是其他)不需要控制器關心,控制器只需要在代理返回的資料可以直接對渲染UI,讓Controller更加輕量化。
發起請求
1 2 3 4 |
NSString *userId = @"1"; GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId]; [api start]; api.delegate = self; |
Delegate回撥
1 2 3 4 5 6 7 |
- (void)requestFinished:(YTKBaseRequest *)request { NSLog(@"----- succeed ---- %@", request.responseJSONObject); //Rendering interface } - (void)requestFailed:(YTKBaseRequest *)request { NSLog(@"failed"); } |
YTKNetwork解析
首先看下YTKNetwork的類檔案:
YTKNetwork.png
圖解它們之間的呼叫關係,注意還是理順關係,看懂這個圖應該對原始碼的理解沒有太多問題:
Scrren.png
YTKBaseRequest
:YTKRequest的父類,定義了Request的相關屬性,Block和Delegate。給對外介面預設的實現,以及公共邏輯。
YTKRequest
:主要對快取做處理,更新快取、讀取快取、手動寫入快取,是否忽略快取。這裡採用歸檔形式快取,請求方式、根路徑、請求地址、請求引數、app版本號、敏感資料拼接再MD5作為快取的檔名,保證唯一性。還提供設定快取的儲存時長,主要實現是通過獲取快取檔案上次修改的時刻距離現在的時間和設定的快取時長作比較,來判斷是否真正發起請求,下面是發起請求的一些邏輯判斷:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
- (void)start { if (self.ignoreCache) { //如果忽略快取 -> 網路請求 [super start]; return; } // check cache time if ([self cacheTimeInSeconds] 網路請求 [super start]; return; } // check cache version long long cacheVersionFileContent = [self cacheVersionFileContent]; if (cacheVersionFileContent != [self cacheVersion]) { //驗證快取版本號,如果不一致 -> 網路請求 [super start]; return; } // check cache existance NSString *path = [self cacheFilePath]; // NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:path isDirectory:nil]) { //根據檔案路徑,驗證快取是否存在,不存在 -> 網路請求 [super start]; return; } // check cache time 上次快取檔案時刻距離現在的時長 與 快取有效時間 對比 int seconds = [self cacheFileDuration:path]; if (seconds [self cacheTimeInSeconds]) { //上次快取檔案時刻距離現在的時長 > 快取有效時間 [super start]; return; } // load cache _cacheJson = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; if (_cacheJson == nil) { //取出快取,如果沒有 -> 網路請求 [super start]; return; } _dataFromCache = YES; //快取請求成功後的資料 [self requestCompleteFilter]; //代理 YTKRequest *strongSelf = self; [strongSelf.delegate requestFinished:strongSelf]; if (strongSelf.successCompletionBlock) { //block回撥 strongSelf.successCompletionBlock(strongSelf); } [strongSelf clearCompletionBlock]; } |
通過歸檔儲存網路請求的資料:
1 2 3 4 5 6 7 8 9 |
- (void)saveJsonResponseToCacheFile:(id)jsonResponse { if ([self cacheTimeInSeconds] > 0 & ![self isDataFromCache]) { NSDictionary *json = jsonResponse; if (json != nil) { [NSKeyedArchiver archiveRootObject:json toFile:[self cacheFilePath]]; [NSKeyedArchiver archiveRootObject:@([self cacheVersion]) toFile:[self cacheVersionFilePath]]; } } } |
YTKNetworkAgent
:真正發起網路請求的類,在addRequest
方法裡呼叫AFN的方法,這塊可以方便的更換第三方庫,還包括一些請求取消,外掛的代理方法呼叫等,所有網路請求失敗或者成功都會呼叫下面這個方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
- (void)handleRequestResult:(AFHTTPRequestOperation *)operation { NSString *key = [self requestHashKey:operation]; YTKBaseRequest *request = _requestsRecord[key]; YTKLog(@"Finished Request: %@", NSStringFromClass([request class])); if (request) { BOOL succeed = [self checkResult:request]; if (succeed) { //請求成功 [request toggleAccessoriesWillStopCallBack]; //呼叫執行載入動畫外掛 [request requestCompleteFilter]; if (request.delegate != nil) { //請求成功代理回撥 [request.delegate requestFinished:request]; } if (request.successCompletionBlock) { //請求成功Block回撥 request.successCompletionBlock(request); } [request toggleAccessoriesDidStopCallBack]; } else { //請求失敗 YTKLog(@"Request %@ failed, status code = %ld", NSStringFromClass([request class]), (long)request.responseStatusCode); [request toggleAccessoriesWillStopCallBack]; //呼叫執行載入動畫外掛 [request requestFailedFilter]; if (request.delegate != nil) { //請求失敗代理回撥 [request.delegate requestFailed:request]; } if (request.failureCompletionBlock) { //請求失敗Block回撥 request.failureCompletionBlock(request); } [request toggleAccessoriesDidStopCallBack]; } } [self removeOperation:operation]; [request clearCompletionBlock]; } |
YTKNetworkConfig
:配置請求根路徑、DNS地址。YTKNetworkPrivate
:可以理解為一個工具類,拼接地址,提供加密方法,定義分類等。YTKBatchRequest
、YTKChainRequest
:這是YKTNetwork的兩個高階用法,批量網路請求和鏈式的網路請求,相當於一個存放Request的容器,先定義下面屬性,finishedCount來記錄批量請求的完成的個數:
1 2 3 |
@interface YTKBatchRequest() @property (nonatomic) NSInteger finishedCount; @end |
每完成一個請求finishedCount++,直到finishedCount等於所有請求的個數時才回撥成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#pragma mark - Network Request Delegate - (void)requestFinished:(YTKRequest *)request { _finishedCount++; if (_finishedCount == _requestArray.count) { [self toggleAccessoriesWillStopCallBack]; if ([_delegate respondsToSelector:@selector(batchRequestFinished:)]) { [_delegate batchRequestFinished:self]; } if (_successCompletionBlock) { _successCompletionBlock(self); } [self clearCompletionBlock]; [self toggleAccessoriesDidStopCallBack]; } } |
給Request繫結一個Index
1 2 3 |
@interface YTKChainRequest() @property (assign, nonatomic) NSUInteger nextRequestIndex; @end |
從requestArray陣列中依次取出發起網路請求,同時nextRequestIndex++,只要一個請求失敗則觸發失敗的回撥:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (void)start { if (_nextRequestIndex > 0) { YTKLog(@"Error! Chain request has already started."); return; } if ([_requestArray count] > 0) { [self toggleAccessoriesWillStartCallBack]; [self startNextRequest]; [[YTKChainRequestAgent sharedInstance] addChainRequest:self]; } else { YTKLog(@"Error! Chain request array is empty."); } } |
1 2 3 |
//下一個網路請求 - (BOOL)startNextRequest { if (_nextRequestIndex |
最後
最近也是看得比寫程式碼多,大都一些原始碼和部落格,種類也比較泛,讀懂作者的思路還是收穫頗多,往往有時候會要上好幾遍,每一遍都有些新的收穫,貴在堅持了。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!