轉(zhuǎn)載鏈接:http://www.reibang.com/p/521a6437a0b6
參考上面鏈接才稍微看懂源碼的蚕苇,同時將原來的老版代碼,換成新版的凿叠;
使用后涩笤,畫出他們的調(diào)用關(guān)系,這樣方便看懂盒件,方便理解
還有其他好多理解文章蹬碧;
猿題庫 iOS 客戶端 網(wǎng)絡(luò)庫封裝
https://github.com/yuantiku/YTKNetwork
YTKNetwork 源碼分析
https://github.com/subvin/YTKNetworkAnalysis
BANetworking
https://github.com/beyondabel/BANetworking
首先
關(guān)于網(wǎng)絡(luò)層最先可能想到的是AFNetworking
,或者Swift中的Alamofire
炒刁,直接使用起來也特別的簡單恩沽,但是稍復(fù)雜的項目如果直接使用就顯得不夠用了,首先第三方耦合不說翔始,就光散落在各處的請求回調(diào)就難以后期維護(hù)罗心,所以一般會有針對性的再次封裝,往往初期可能業(yè)務(wù)相對簡單城瞎,考慮的方面較少渤闷,后期業(yè)務(wù)增加可能需要對網(wǎng)絡(luò)層進(jìn)行重構(gòu),一個好的架構(gòu)也一定是和業(yè)務(wù)層面緊密相連的脖镀,隨業(yè)務(wù)的增長不斷健壯的飒箭。
最近也是看了YTKNetwork
的源碼和相關(guān)博客,站在前輩的肩膀上寫下一些自己關(guān)于網(wǎng)絡(luò)層的解讀。
與業(yè)務(wù)層對接方式
常見的與業(yè)務(wù)層對接方式兩種:
- 集約型:
最典型就屬于上面說的AFNetworking
弦蹂、Alamofire
肩碟,發(fā)起網(wǎng)絡(luò)請求都集中在一個類上,請求回調(diào)通過Block凸椿、閉包實現(xiàn)的削祈,Block、閉包回調(diào)有比較好的靈活性削饵,可以方便的在任何位置發(fā)起請求岩瘦,同時也可能是不好的地方未巫,網(wǎng)絡(luò)請求回調(diào)散落在各處窿撬,不便于維護(hù)。
下面是一個集約型的網(wǎng)絡(luò)請求叙凡,大家使用集約型網(wǎng)絡(luò)請求有沒有遇到這么一個場景逸吵,請求回調(diào)后需要做比較多的處理窿侈,代碼量多的時候,會再定義一個私有方法把代碼寫在里面,在Block中調(diào)用在私有方法搁料。其實這樣處理本質(zhì)上和通過代理回調(diào)本質(zhì)上是一樣的。
[_manager GET:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
//The data processing, Rendering interface
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
- 離散型:
對應(yīng)的是接下來的YTKNetwork
伤靠,離散型最大的特點(diǎn)就是一個網(wǎng)絡(luò)請求對應(yīng)一個單獨(dú)的類范咨,在這個類內(nèi)部封裝請求地址、方式燥撞、參數(shù)座柱、校驗和處理請求回來的數(shù)據(jù),通常代理回調(diào)物舒,需要跨層數(shù)據(jù)傳遞時也使用通知回調(diào)色洞,比較集中,因為數(shù)據(jù)處理都放在內(nèi)部處理了冠胯,返回數(shù)據(jù)的形式(模型化后的數(shù)據(jù)還是其他)不需要控制器關(guān)心火诸,控制器只需要在代理返回的數(shù)據(jù)可以直接對渲染UI,讓Controller更加輕量化荠察。
發(fā)起請求
NSString *userId = @"1";
GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId];
[api start];
api.delegate = self;
Delegate回調(diào)
- (void)requestFinished:(YTKBaseRequest *)request {
NSLog(@"----- succeed ---- %@", request.responseJSONObject);
//Rendering interface
}
- (void)requestFailed:(YTKBaseRequest *)request {
NSLog(@"failed");
}
YTKNetwork解析
首先看下YTKNetwork的類文件:
圖解它們之間的調(diào)用關(guān)系,注意還是理順關(guān)系悉盆,看懂這個圖應(yīng)該對源碼的理解沒有太多問題:
YTKBaseRequest
:YTKRequest的父類,定義了Request的相關(guān)屬性舀瓢,Block和Delegate。給對外接口默認(rèn)的實現(xiàn),以及公共邏輯航缀。YTKRequest
:主要對緩存做處理,更新緩存芥玉、讀取緩存、手動寫入緩存灿巧,是否忽略緩存。這里采用歸檔形式緩存抠藕,請求方式、根路徑盾似、請求地址、請求參數(shù)零院、app版本號溉跃、敏感數(shù)據(jù)拼接再M(fèi)D5作為緩存的文件名,保證唯一性告抄。還提供設(shè)置緩存的保存時長撰茎,主要實現(xiàn)是通過獲取緩存文件上次修改的時刻距離現(xiàn)在的時間和設(shè)置的緩存時長作比較,來判斷是否真正發(fā)起請求打洼,下面是發(fā)起請求的一些邏輯判斷:
- (void)start {
if (self.ignoreCache) // 如果忽略緩存 --> 網(wǎng)絡(luò)請求
{
[self startWithoutCache];
return;
}
// Do not cache download request.
if (self.resumableDownloadPath) // 不會緩存下載請求 --> 網(wǎng)絡(luò)請求
{
[self startWithoutCache];
return;
}
if (![self loadCacheWithError:nil]) // 從緩存加載出錯 --> 網(wǎng)絡(luò)請求
{
[self startWithoutCache];
return;
}
_dataFromCache = YES; // 從緩存加載數(shù)據(jù)
dispatch_async(dispatch_get_main_queue(), ^{
[self requestCompletePreprocessor];
[self requestCompleteFilter];
YTKRequest *strongSelf = self;
[strongSelf.delegate requestFinished:strongSelf];
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
});
}
通過歸檔存儲網(wǎng)絡(luò)請求的數(shù)據(jù):
- (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);
}
}
}
}
-
YTKNetworkAgent
:真正發(fā)起網(wǎng)絡(luò)請求的類龄糊,在addRequest
方法里調(diào)用AFN的方法,這塊可以方便的更換第三方庫拟蜻,還包括一些請求取消绎签,插件的代理方法調(diào)用等,所有網(wǎng)絡(luò)請求失敗或者成功都會調(diào)用下面這個方法:
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
Lock();
YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)]; // 從dic中獲取request
Unlock();
// When the request is cancelled and removed from records, the underlying
// AFNetworking failure callback will still kicks in, resulting in a nil `request`.
//
// Here we choose to completely ignore cancelled tasks. Neither success or failure
// callback will be called.
if (!request) {
return;
}
YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));
NSError * __autoreleasing serializationError = nil;
NSError * __autoreleasing validationError = nil;
NSError *requestError = nil;
BOOL succeed = NO;
request.responseObject = responseObject;
if ([request.responseObject isKindOfClass:[NSData class]]) {
request.responseData = responseObject;
request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
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;
}
}
if (error) {
succeed = NO;
requestError = error;
} else if (serializationError) {
succeed = NO;
requestError = serializationError;
} else {
succeed = [self validateResult:request error:&validationError];
requestError = validationError;
}
if (succeed) // 請求成功
{
[self requestDidSucceedWithRequest:request];
} else // 請求失敗
{
[self requestDidFailWithRequest:request error:requestError];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self removeRequestFromRecord:request]; // 從dic中移除request
[request clearCompletionBlock]; // block置nil酝锅,可以打破循環(huán)引用诡必;
});
}
YTKNetworkConfig
:配置請求根路徑、DNS地址搔扁。YTKNetworkPrivate
:可以理解為一個工具類爸舒,拼接地址,提供加密方法稿蹲,定義分類等扭勉。YTKBatchRequest
、YTKChainRequest
:這是YKTNetwork的兩個高級用法苛聘,批量網(wǎng)絡(luò)請求和鏈?zhǔn)降木W(wǎng)絡(luò)請求涂炎,相當(dāng)于一個存放Request的容器忠聚,先定義下面屬性,
YTKBatchRequest
finishedCount來記錄批量請求的完成的個數(shù):
@interface YTKBatchRequest() <YTKRequestDelegate>
@property (nonatomic) NSInteger finishedCount;
@end
每完成一個請求finishedCount++唱捣,直到finishedCount等于所有請求的個數(shù)時才回調(diào)成功两蟀。
#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];
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
}
YTKChainRequest
給Request綁定一個Index
@interface YTKChainRequest()<YTKRequestDelegate>
@property (assign, nonatomic) NSUInteger nextRequestIndex;
@end
從requestArray數(shù)組中依次取出發(fā)起網(wǎng)絡(luò)請求,同時nextRequestIndex++震缭,只要一個請求失敗則觸發(fā)失敗的回調(diào):
- (void)start {
if (_nextRequestIndex > 0) {
YTKLog(@"Error! Chain request has already started.");
return;
}
if ([_requestArray count] > 0) {
[self toggleAccessoriesWillStartCallBack];
[self startNextRequest];
[[YTKChainRequestAgent sharedAgent] addChainRequest:self];
} else {
YTKLog(@"Error! Chain request array is empty.");
}
}
//下一個網(wǎng)絡(luò)請求
- (BOOL)startNextRequest {
if (_nextRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[_nextRequestIndex];
_nextRequestIndex++;
request.delegate = self;
[request clearCompletionBlock];
[request start];
return YES;
} else {
return NO;
}
}
最后
最近也是看得比寫代碼多赂毯,大都一些源碼和博客,種類也比較泛拣宰,讀懂作者的思路還是收獲頗多党涕,往往有時候會要上好幾遍,每一遍都有些新的收獲巡社,貴在堅持了https://github.com/ShelinShelin/SourceCodeAnalyze膛堤。