YTKNetwork是一個(gè)基于AFNetworking的網(wǎng)絡(luò)層封裝,包括以下幾個(gè)核心的類
- YTKBaseRequest
- YTKRequest
- YTKNetworkAgent
- YTKNetworkConfig
YTKNetwork的基本用法:基本用法,YTKNetwork 的基本的思想是把每一個(gè)網(wǎng)絡(luò)請(qǐng)求封裝成對(duì)象。所以使用 YTKNetwork,你的每一種請(qǐng)求都需要繼承 YTKRequest類,通過(guò)覆蓋父類的一些方法來(lái)構(gòu)造指定的網(wǎng)絡(luò)請(qǐng)求。
下面會(huì)分別講解這幾個(gè)類。
YTKNetworkAgent
網(wǎng)絡(luò)請(qǐng)求的總代理類,是對(duì)AFNetworking的封裝。此類是一個(gè)單例。
內(nèi)部包含的三個(gè)成員變量:
- AFHTTPRequestOperationManager *_manager;
- AFHTTPRequestOperationManagerd的單例網(wǎng)路請(qǐng)求manager對(duì)象
- YTKNetworkConfig *_config;
- 負(fù)責(zé)配置一個(gè)相關(guān)的設(shè)置
- NSMutableDictionary *_requestsRecord;
- 請(qǐng)求隊(duì)列
對(duì)外的接口有以下方法:
- -(void)addRequest:(YTKBaseRequest *)request;
// 這里詳細(xì)分析一下addRequest的內(nèi)部實(shí)現(xiàn)
- (void)addRequest:(YTKBaseRequest *)request {
YTKRequestMethod method = [request requestMethod];
NSString *url = [self buildRequestUrl:request];
id param = request.requestArgument;
AFConstructingBlock constructingBlock = [request constructingBodyBlock];
// 設(shè)置返回對(duì)象的格式,YTKRequestSerializerTypeHTTP代表返回二進(jìn)制格式,YTKRequestSerializerTypeJSON代表返回一個(gè)json的根對(duì)象(NSDictionary或者NSArray)
if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
_manager.requestSerializer = [AFHTTPRequestSerializer serializer];
} else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
_manager.requestSerializer = [AFJSONRequestSerializer serializer];
}
_manager.requestSerializer.timeoutInterval = [request requestTimeoutInterval];
// 如果請(qǐng)求需要授權(quán)證書,這里設(shè)置用戶名和密碼
NSArray *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
if (authorizationHeaderFieldArray != nil) {
[_manager.requestSerializer setAuthorizationHeaderFieldWithUsername:(NSString *)authorizationHeaderFieldArray.firstObject
password:(NSString *)authorizationHeaderFieldArray.lastObject];
}
// 設(shè)置其他HTTP header
NSDictionary *headerFieldValueDictionary = [request requestHeaderFieldValueDictionary];
if (headerFieldValueDictionary != nil) {
for (id httpHeaderField in headerFieldValueDictionary.allKeys) {
id value = headerFieldValueDictionary[httpHeaderField];
if ([httpHeaderField isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) {
[_manager.requestSerializer setValue:(NSString *)value forHTTPHeaderField:(NSString *)httpHeaderField];
} else {
YTKLog(@"Error, class of key/value in headerFieldValueDictionary should be NSString.");
}
}
}
// 如果創(chuàng)建了自定義的NSURLRequest對(duì)象,就使用自定的對(duì)象
NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
if (customUrlRequest) {
// 創(chuàng)建 AFHTTPRequestOperation 對(duì)象
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:customUrlRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *op, id responseObject) {
// 處理結(jié)果
[self handleRequestResult:op];
} failure:^(AFHTTPRequestOperation *op, NSError *error) {
[self handleRequestResult:op];
}];
request.requestOperation = operation;
operation.responseSerializer = _manager.responseSerializer;
// 添加到請(qǐng)求隊(duì)列
[_manager.operationQueue addOperation:operation];
} else {
// 沒(méi)有自定義NSURLRequest,需要手動(dòng)創(chuàng)建
if (method == YTKRequestMethodGet) {
// 如果需要斷點(diǎn)續(xù)傳下載文件
if (request.resumableDownloadPath) {
// 拼接參數(shù)到url
NSString *filteredUrl = [YTKNetworkPrivate urlStringWithOriginUrlString:url appendParameters:param];
NSURLRequest *requestUrl = [NSURLRequest requestWithURL:[NSURL URLWithString:filteredUrl]];
AFDownloadRequestOperation *operation = [[AFDownloadRequestOperation alloc] initWithRequest:requestUrl targetPath:request.resumableDownloadPath shouldResume:YES];
// 設(shè)置斷點(diǎn)續(xù)傳的進(jìn)度回調(diào)block
[operation setProgressiveDownloadProgressBlock:request.resumableDownloadProgressBlock];
// 整個(gè)請(qǐng)求完成的回調(diào)block
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *op, id responseObject) {
[self handleRequestResult:op];
} failure:^(AFHTTPRequestOperation *op, NSError *error) {
[self handleRequestResult:op];
}];
request.requestOperation = operation;
[_manager.operationQueue addOperation:operation];
} else {
request.requestOperation = [_manager GET:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self handleRequestResult:operation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self handleRequestResult:operation];
}];
}
} else if (method == YTKRequestMethodPost) {
if (constructingBlock != nil) {
// constructingBlock是一個(gè)返回實(shí)現(xiàn)AFMultipartFormData協(xié)議的對(duì)象,該對(duì)象主要作用是實(shí)現(xiàn)文件上傳
// 我們通常會(huì)上傳圖片或者文件需要用到multipart/form-data,實(shí)現(xiàn)以下即可:
/*
- (AFConstructingBlock)constructingBodyBlock {
return ^(id<AFMultipartFormData> formData) {
NSData *data = UIImageJPEGRepresentation([UIImage imageNamed:@"currentPageDot"], 0.9);
NSString *name = @"image";
NSString *formKey = @"image";
NSString *type = @"image/jpeg";
[formData appendPartWithFileData:data name:formKey fileName:name mimeType:type];
};
}*/
request.requestOperation = [_manager POST:url parameters:param constructingBodyWithBlock:constructingBlock success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self handleRequestResult:operation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self handleRequestResult:operation];
}];
} else {
request.requestOperation = [_manager POST:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self handleRequestResult:operation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self handleRequestResult:operation];
}];
}
} else if (method == YTKRequestMethodHead) {
// 只返回head的請(qǐng)求
request.requestOperation = [_manager HEAD:url parameters:param success:^(AFHTTPRequestOperation *operation) {
[self handleRequestResult:operation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self handleRequestResult:operation];
}];
} else if (method == YTKRequestMethodPut) {
// 更新資源的請(qǐng)求
request.requestOperation = [_manager PUT:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self handleRequestResult:operation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self handleRequestResult:operation];
}];
} else if (method == YTKRequestMethodDelete) {
// 刪除資源
request.requestOperation = [_manager DELETE:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self handleRequestResult:operation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self handleRequestResult:operation];
}];
} else if (method == YTKRequestMethodPatch) {
// 對(duì)PUT請(qǐng)求的補(bǔ)充,更新部分資源
request.requestOperation = [_manager PATCH:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self handleRequestResult:operation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self handleRequestResult:operation];
}];
} else {
YTKLog(@"Error, unsupport method type");
return;
}
}
// 添加一個(gè)請(qǐng)求到_requestsRecord字典中,key是AFHTTPRequestOperation的hash值,value是YTKBaseRequest對(duì)象
// _requestsRecord的作用:當(dāng)請(qǐng)求完成時(shí),AFN返回operation,通過(guò)_requestsRecord可以反射出它所屬的YTKBaseRequest對(duì)象
[self addOperation:request];
}
- -(void)cancelRequest:(YTKBaseRequest *)request;
- (void)cancelRequest:(YTKBaseRequest *)request {
[request.requestOperation cancel];
[self removeOperation:request.requestOperation];
[request clearCompletionBlock];
}
- -(void)cancelAllRequests;
- (void)cancelAllRequests {
NSDictionary *copyRecord = [_requestsRecord copy];
for (NSString *key in copyRecord) {
YTKBaseRequest *request = copyRecord[key];
[request stop];
}
}
- -(NSString *)buildRequestUrl:(YTKBaseRequest *)request;
YTKBaseRequest
YTKRequest的父類,先介紹它幾個(gè)重要的屬性和方法。
需要子類來(lái)重寫的方法
-
/// 請(qǐng)求成功的回調(diào)
- -(void)requestCompleteFilter;
-
/// 請(qǐng)求失敗的回調(diào)
- -(void)requestFailedFilter;
-
/// 請(qǐng)求的URL
- -(NSString *)requestUrl;
-
/// 請(qǐng)求的CdnURL
- -(NSString *)cdnUrl;
/// 請(qǐng)求的BaseURL
(NSString *)baseUrl;
-
/// 請(qǐng)求的連接超時(shí)時(shí)間,默認(rèn)為60秒
- -(NSTimeInterval)requestTimeoutInterval;
-
/// 請(qǐng)求的參數(shù)列表
- -(id)requestArgument;
-
/// 用于在cache結(jié)果,計(jì)算cache文件名時(shí),忽略掉一些指定的參數(shù)
- -(id)cacheFileNameFilterForRequestArgument:(id)argument;
-
/// Http請(qǐng)求的方法
- -(YTKRequestMethod)requestMethod;
-
/// 請(qǐng)求的SerializerType
- -(YTKRequestSerializerType)requestSerializerType;
-
/// 請(qǐng)求的Server用戶名和密碼
- -(NSArray *)requestAuthorizationHeaderFieldArray;
-
/// 在HTTP報(bào)頭添加的自定義參數(shù)
- -(NSDictionary *)requestHeaderFieldValueDictionary;
/// 構(gòu)建自定義的UrlRequest,
-
/// 若這個(gè)方法返回非nil對(duì)象,會(huì)忽略requestUrl, requestArgument, requestMethod, requestSerializerType
- -(NSURLRequest *)buildCustomUrlRequest;
-
/// 是否使用CDN的host地址
- -(BOOL)useCDN;
-
/// 用于檢查JSON是否合法的對(duì)象
- -(id)jsonValidator;
-
/// 用于檢查Status Code是否正常的方法
- -(BOOL)statusCodeValidator;
-
/// 當(dāng)POST的內(nèi)容帶有文件等富文本時(shí)使用
- -(AFConstructingBlock)constructingBodyBlock;
-
/// 當(dāng)需要斷點(diǎn)續(xù)傳時(shí),指定續(xù)傳的地址
- -(NSString *)resumableDownloadPath;
-
/// 當(dāng)需要斷點(diǎn)續(xù)傳時(shí),獲得下載進(jìn)度的回調(diào)
- -(AFDownloadProgressBlock)resumableDownloadProgressBlock;
里面重要的兩個(gè)方法:
- (void)start {
// 調(diào)用即將開始請(qǐng)求的hook
[self toggleAccessoriesWillStartCallBack];
[[YTKNetworkAgent sharedInstance] addRequest:self];
}
/// remove self from request queue
- (void)stop {
// 即將結(jié)束的hook
[self toggleAccessoriesWillStopCallBack];
self.delegate = nil;
[[YTKNetworkAgent sharedInstance] cancelRequest:self];
[self toggleAccessoriesDidStopCallBack];
}
還有一個(gè)比較重要的增加hook的方法,需要自定義個(gè)對(duì)象,實(shí)現(xiàn)YTKRequestAccessory協(xié)議定義的一些方法來(lái)hook一些動(dòng)作
- (void)addAccessory:(id<YTKRequestAccessory>)accessory {
// 因?yàn)榭赡苡卸鄠€(gè)hook對(duì)象,所以添加到一個(gè)數(shù)組中,調(diào)用的時(shí)候也是遍歷數(shù)組調(diào)用
if (!self.requestAccessories) {
self.requestAccessories = [NSMutableArray array];
}
[self.requestAccessories addObject:accessory];
}
YTKRequest
這里主要實(shí)現(xiàn)了一些緩存策略,重寫了父類的start方法
- (void)start {
if (self.ignoreCache) {
[super start];
return;
}
// 查看緩存時(shí)間是否過(guò)期
if ([self cacheTimeInSeconds] < 0) {
[super start];
return;
}
// 查看本地的緩存版本號(hào)和當(dāng)前緩存判斷是否匹配
long long cacheVersionFileContent = [self cacheVersionFileContent];
if (cacheVersionFileContent != [self cacheVersion]) {
[super start];
return;
}
// 查看緩存文件是否存在
NSString *path = [self cacheFilePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:path isDirectory:nil]) {
[super start];
return;
}
// 查看緩存時(shí)間是否過(guò)期
int seconds = [self cacheFileDuration:path];
if (seconds < 0 || seconds > [self cacheTimeInSeconds]) {
[super start];
return;
}
// 加載緩存數(shù)據(jù)
_cacheJson = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (_cacheJson == nil) {
[super start];
return;
}
_dataFromCache = YES;
// 調(diào)用緩存結(jié)束回調(diào)
[self requestCompleteFilter];
YTKRequest *strongSelf = self;
[strongSelf.delegate requestFinished:strongSelf];
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
}
緩存是存放在本地文件中的,文件名用一些關(guān)鍵字的字符串拼接并md5來(lái)表示:
- (NSString *)cacheFileName {
NSString *requestUrl = [self requestUrl];
NSString *baseUrl = [YTKNetworkConfig sharedInstance].baseUrl;
id argument = [self cacheFileNameFilterForRequestArgument:[self requestArgument]];
NSString *requestInfo = [NSString stringWithFormat:@"Method:%ld Host:%@ Url:%@ Argument:%@ AppVersion:%@ Sensitive:%@", (long)[self requestMethod], baseUrl, requestUrl,
argument, [YTKNetworkPrivate appVersionString], [self cacheSensitiveData]];
NSString *cacheFileName = [YTKNetworkPrivate md5StringFromString:requestInfo];
return cacheFileName;
}
YTKNetworkConfig
這個(gè)類主要負(fù)責(zé)一些配置的工作,配置baseUrl,cdnUrl等工作,內(nèi)部沒(méi)有什么具體的實(shí)現(xiàn),在其他類中獲取這個(gè)類的配置信息
YTKBatchRequestAgent、YTKBatchRequest
用于方便地發(fā)送批量的網(wǎng)絡(luò)請(qǐng)求,YTKBatchRequest是一個(gè)容器類,它可以放置多個(gè) YTKRequest 子類,并統(tǒng)一處理這多個(gè)網(wǎng)絡(luò)請(qǐng)求的成功和失敗。
在如下的示例中,我們發(fā)送了4個(gè)批量的請(qǐng)求,并統(tǒng)一處理這4個(gè)請(qǐng)求同時(shí)成功的回調(diào)。
- (void)sendBatchRequest {
GetImageApi *a = [[GetImageApi alloc] initWithImageId:@"1.jpg"];
GetImageApi *b = [[GetImageApi alloc] initWithImageId:@"2.jpg"];
GetImageApi *c = [[GetImageApi alloc] initWithImageId:@"3.jpg"];
GetUserInfoApi *d = [[GetUserInfoApi alloc] initWithUserId:@"123"];
YTKBatchRequest *batchRequest = [[YTKBatchRequest alloc] initWithRequestArray:@[a, b, c, d]];
[batchRequest startWithCompletionBlockWithSuccess:^(YTKBatchRequest *batchRequest) {
NSLog(@"succeed");
NSArray *requests = batchRequest.requestArray;
GetImageApi *a = (GetImageApi *)requests[0];
GetImageApi *b = (GetImageApi *)requests[1];
GetImageApi *c = (GetImageApi *)requests[2];
GetUserInfoApi *user = (GetUserInfoApi *)requests[3];
// deal with requests result ...
} failure:^(YTKBatchRequest *batchRequest) {
NSLog(@"failed");
}];
}
內(nèi)部實(shí)現(xiàn),start方法遍歷所有request,并調(diào)用start方法
- (void)start {
if (_finishedCount > 0) {
YTKLog(@"Error! Batch request has already started.");
return;
}
[[YTKBatchRequestAgent sharedInstance] addBatchRequest:self];
[self toggleAccessoriesWillStartCallBack];
for (YTKRequest * req in _requestArray) {
req.delegate = self;
[req start];
}
}
在成功回調(diào)中,有一個(gè)計(jì)數(shù)器,判斷所有請(qǐng)求是否都已經(jīng)完成
- (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];
}
}
YTKChainRequestAgent、YTKChainRequest
用于管理有相互依賴的網(wǎng)絡(luò)請(qǐng)求,它實(shí)際上最終可以用來(lái)管理多個(gè)拓?fù)渑判蚝蟮木W(wǎng)絡(luò)請(qǐng)求。
以下是具體的代碼示例,在示例中,我們?cè)趕endChainRequest方法中設(shè)置好了Api相互的依賴,然后。 我們就可以通過(guò)chainRequestFinished回調(diào)來(lái)處理所有網(wǎng)絡(luò)請(qǐng)求都發(fā)送成功的邏輯了。如果有任何其中一個(gè)網(wǎng)絡(luò)請(qǐng)求失敗了,則會(huì)觸發(fā)chainRequestFailed回調(diào)。
- (void)sendChainRequest {
RegisterApi *reg = [[RegisterApi alloc] initWithUsername:@"username" password:@"password"];
YTKChainRequest *chainReq = [[YTKChainRequest alloc] init];
[chainReq addRequest:reg callback:^(YTKChainRequest *chainRequest, YTKBaseRequest *baseRequest) {
RegisterApi *result = (RegisterApi *)baseRequest;
NSString *userId = [result userId];
GetUserInfoApi *api = [[GetUserInfoApi alloc] initWithUserId:userId];
[chainRequest addRequest:api callback:nil];
}];
chainReq.delegate = self;
// start to send request
[chainReq start];
}
- (void)chainRequestFinished:(YTKChainRequest *)chainRequest {
// all requests are done
}
- (void)chainRequestFailed:(YTKChainRequest *)chainRequest failedBaseRequest:(YTKBaseRequest*)request {
// some one of request is failed
}
內(nèi)部實(shí)現(xiàn),定義一個(gè)_nextRequestIndex,初始化為0,_requestArray請(qǐng)求數(shù)組,_requestCallbackArray請(qǐng)求回調(diào)數(shù)組
- (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.");
}
}
// 順序執(zhí)行請(qǐng)求,_nextRequestIndex++
- (BOOL)startNextRequest {
if (_nextRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[_nextRequestIndex];
_nextRequestIndex++;
request.delegate = self;
[request start];
return YES;
} else {
return NO;
}
}
// 請(qǐng)求成功回調(diào)
- (void)requestFinished:(YTKBaseRequest *)request {
// 獲取當(dāng)前請(qǐng)求的回調(diào),并調(diào)用其回調(diào),回調(diào)中需要用戶自己去再次去add一個(gè)新的request
NSUInteger currentRequestIndex = _nextRequestIndex - 1;
ChainCallback callback = _requestCallbackArray[currentRequestIndex];
callback(self, request);
// 當(dāng)不能繼續(xù)執(zhí)行請(qǐng)求時(shí),結(jié)束本次chain請(qǐng)求,調(diào)用完成回調(diào)
if (![self startNextRequest]) {
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(chainRequestFinished:)]) {
[_delegate chainRequestFinished:self];
[[YTKChainRequestAgent sharedInstance] removeChainRequest:self];
}
[self toggleAccessoriesDidStopCallBack];
}
}
YTKNetworkPrivate
定義一些內(nèi)部使用的工具方法