本文授權轉載:Shelin(簡書)
首先
關於網絡層最先可能想到的是AFNetworking,或者Swift中的Alamofire,直接使用起來也特別的簡單,但是稍復雜的項目如果直接使用就顯得不夠用了,首先第三方耦合不說,就光散落在各處的請求回調就難以後期維護,所以一般會有針對性的再次封裝,往往初期可能業務相對簡單,考慮的方面較少,後期業務增加可能需要對網絡層進行重構,一個好的架構也一定是和業務層面緊密相連的,隨業務的增長不斷健壯的。
最近也是看了YTKNetwork的源碼和相關博客,站在前輩的肩膀上寫下一些自己關於網絡層的解讀。
與業務層對接方式
常見的與業務層對接方式兩種:
集約型:
最典型就屬於上面說的AFNetworking、Alamofire,發起網絡請求都集中在一個類上,請求回調通過Block、閉包實現的,Block、閉包回調有比較好的靈活性,可以方便的在任何位置發起請求,同時也可能是不好的地方,網絡請求回調散落在各處,不便於維護。
下面是一個集約型的網絡請求,大家使用集約型網絡請求有沒有遇到這麼一個場景,請求回調後需要做比較多的處理,代碼量多的時候,會再定義一個私有方法把代碼寫在裡面,在Block中調用在私有方法。其實這樣處理本質上和通過代理回調本質上是一樣的。
[_manager GET:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) { //The data processing, Rendering interface } failure:^(AFHTTPRequestOperation *operation, NSError *error) { }];
離散型:
對應的是接下來的YTKNetwork,離散型最大的特點就是一個網絡請求對應一個單獨的類,在這個類內部封裝請求地址、方式、參數、校驗和處理請求回來的數據,通常代理回調,需要跨層數據傳遞時也使用通知回調,比較集中,因為數據處理都放在內部處理了,返回數據的形式(模型化後的數據還是其他)不需要控制器關心,控制器只需要在代理返回的數據可以直接對渲染UI,讓Controller更加輕量化。
發起請求
NSString *userId = @"1"; GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId]; [api start]; api.delegate = self;
Delegate回調
- (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作為緩存的文件名,保證唯一性。還提供設置緩存的保存時長,主要實現是通過獲取緩存文件上次修改的時刻距離現在的時間和設置的緩存時長作比較,來判斷是否真正發起請求,下面是發起請求的一些邏輯判斷:
- (void)start { if (self.ignoreCache) { //如果忽略緩存 -> 網絡請求 [super start]; return; } // check cache time if ([self cacheTimeInSeconds] < 0) { //驗證緩存有效時間 -> 網絡請求 [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 < 0 || 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]; }
通過歸檔存儲網絡請求的數據:
- (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的方法,這塊可以方便的更換第三方庫,還包括一些請求取消,插件的代理方法調用等,所有網絡請求失敗或者成功都會調用下面這個方法:
- (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來記錄批量請求的完成的個數:
@interface YTKBatchRequest() @property (nonatomic) NSInteger finishedCount; @end
每完成一個請求finishedCount++,直到finishedCount等於所有請求的個數時才回調成功。
#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
@interface YTKChainRequest()@property (assign, nonatomic) NSUInteger nextRequestIndex; @end
從requestArray數組中依次取出發起網絡請求,同時nextRequestIndex++,只要一個請求失敗則觸發失敗的回調:
- (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."); } } //下一個網絡請求 - (BOOL)startNextRequest { if (_nextRequestIndex < [_requestArray count]) { YTKBaseRequest *request = _requestArray[_nextRequestIndex]; _nextRequestIndex++; request.delegate = self; [request start]; return YES; } else { return NO; } }
最後
最近也是看得比寫代碼多,大都一些源碼和博客,種類也比較泛,讀懂作者的思路還是收獲頗多,往往有時候會要上好幾遍,每一遍都有些新的收獲,貴在堅持了。