前言
本文參考casatwy先生的網(wǎng)絡(luò)層架構(gòu)設(shè)計從網(wǎng)絡(luò)請求的構(gòu)建到請求結(jié)果的處理為你概述如何構(gòu)建一個方便易用的iOS網(wǎng)絡(luò)層, 全文約8千字, 預(yù)計花費閱讀時間20 - 30分鐘.
目錄
網(wǎng)絡(luò)請求的構(gòu)建
網(wǎng)絡(luò)請求的派發(fā)
1.請求的派發(fā)與取消
2.多服務(wù)器的切換
合理的使用請求派發(fā)器
1.協(xié)議還是配置對象?
2.簡單的請求結(jié)果緩存器
3.請求結(jié)果的格式化
4.兩個小玩意兒
一、網(wǎng)絡(luò)請求的構(gòu)建
網(wǎng)絡(luò)請求的構(gòu)建很簡單, 根據(jù)一個請求需要的條件如URL, 請求方式, 請求參數(shù), 請求頭等定義請求生成的接口即可. 定義如下:
| 1234567891011121314151617181920 | @interface HHURLRequestGenerator : NSObject + (instancetype)sharedInstance; - (void)switchService;- (void)switchToService:(HHServiceType)serviceType; - (NSMutableURLRequest *)generateRequestWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps method:(NSString *)method params:(NSDictionary *)params header:(NSDictionary *)header; - (NSMutableURLRequest *)generateUploadRequestUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps params:(NSDictionary *)params contents:(NSArray*)contents header:(NSDictionary *)header; @end |
可以看到方法參數(shù)都是生成請求基本組成部分, 當(dāng)然, 這里的參數(shù)比較少, 因為在我的項目中像請求超時時間都是一樣的, 類似這些公用的設(shè)置我都偷懶直接寫在請求配置文件里面了. 我們看看請求接口的具體實現(xiàn), 以數(shù)據(jù)請求為例:
| 123456789101112 | - (NSMutableURLRequest *)generateRequestWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps method:(NSString *)method params:(NSDictionary *)params header:(NSDictionary *)header { NSString *urlString = [self urlStringWithPath:urlPath useHttps:useHttps]; NSMutableURLRequest *request = [self.requestSerialize requestWithMethod:method URLString:urlString parameters:params error:nil]; request.timeoutInterval = RequestTimeoutInterval; [self setCookies];//設(shè)置cookie [self setCommonRequestHeaderForRequest:request];// 在這里做公用請求頭的設(shè)置 [header enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) { [request setValue:value forHTTPHeaderField:key]; }]; return request;} |
| 12345678910111213141516 | - (NSString *)urlStringWithPath:(NSString *)path useHttps:(BOOL)useHttps { if ([path hasprefix:@"http"]) { return path; } else { NSString *baseUrlString = [HHService currentService].baseUrl; if (useHttps && baseUrlString.length > 4) { NSMutableString *mString = [NSMutableString stringWithString:baseUrlString]; [mString insertString:@"s" atIndex:4]; baseUrlString = [mString copy]; } return [NSString stringWithFormat:@"%@%@", baseUrlString, path]; }} |
代碼很簡單, 接口根據(jù)參數(shù)調(diào)用urlStringWithPath:useHttps:通過BaseURL和URLPath拼裝出完整的URL, 然后用這個URL和其他參數(shù)生成一個URLRequest, 然后調(diào)用setCommonRequestHeaderForRequest:設(shè)置公用請求, 最后返回這個URLRequest.
BaseURL來自HHService, HHService對外暴露各個環(huán)境(測試/開發(fā)/發(fā)布)下的baseURL和切換服務(wù)器的接口, 內(nèi)部走工廠生成當(dāng)前的服務(wù)器, 我的設(shè)置是默認(rèn)連接第一個服務(wù)器且APP關(guān)閉后恢復(fù)此設(shè)置, APP運行中可根據(jù)需要調(diào)用switchService切換服務(wù)器.
HHService定義如下:
| 12345678910111213141516171819 | @PRotocol HHService @optional- (NSString *)testEnvironmentBaseUrl;- (NSString *)developEnvironmentBaseUrl;- (NSString *)releaseEnvironmentBaseUrl; @end @interface HHService : NSObject + (HHService *)currentService; + (void)switchService;+ (void)switchToService:(HHServiceType)serviceType; - (NSString *)baseUrl;- (HHServiceEnvironment)environment;@end |
| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 | #import "HHService.h" @interface HHService () @property (assign, nonatomic) HHServiceType type;@property (assign, nonatomic) HHServiceEnvironment environment; @end @interface HHServiceX : HHService@end @interface HHServiceY : HHService@end @interface HHServiceZ : HHService@end @implementation HHService #pragma mark - Interface static HHService *currentService;static dispatch_semaphore_t lock;+ (HHService *)currentService { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ lock = dispatch_semaphore_create(1); currentService = [HHService serviceWithType:HHService0]; }); return currentService;} + (void)switchService { [self switchToService:self.currentService.type + 1];} + (void)switchToService:(HHServiceType)serviceType { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); currentService = [HHService serviceWithType:(serviceType % ServiceCount)]; dispatch_semaphore_signal(lock);} + (HHService *)serviceWithType:(HHServiceType)type { HHService *service; switch (type) { case HHService0: service = [HHServiceX new]; break; case HHService1: service = [HHServiceY new]; break; case HHService2: service = [HHServiceZ new]; break; } service.type = type; service.environment = BulidServiceEnvironment; return service;} - (NSString *)baseUrl { switch (self.environment) { case HHServiceEnvironmentTest: return [self testEnvironmentBaseUrl]; case HHServiceEnvironmentDevelop: return [self developEnvironmentBaseUrl]; case HHServiceEnvironmentRelease: return [self releaseEnvironmentBaseUrl]; }} @end |
二、網(wǎng)絡(luò)請求的派發(fā)
請求的派發(fā)是通過一個單例HHNetworkClient來實現(xiàn)的, 如果把請求比作炮彈的話, 那么這個單例就是發(fā)射炮彈的炮臺, 使用炮臺的人只需要告訴炮臺需要發(fā)射什么樣的炮彈和炮彈的打擊目標(biāo)便可發(fā)射了. 另外, 應(yīng)該提供取消打擊的功能以處理不必要的打擊的情況, 那么, 根據(jù)炮臺的作用.
HHNetworkClient定義如下:
| 12345678910111213141516171819202122232425262728293031 | @interface HHNetworkClient : NSObject + (instancetype)sharedInstance; - (NSURLsessionDataTask *)dataTaskWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps requestType:(HHNetworkRequestType)requestType params:(NSDictionary *)params header:(NSDictionary *)header completionHandler:(void (^)(NSURLResponse *response,id responSEObject,NSError *error))completionHandler; - (NSNumber *)dispatchTaskWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps requestType:(HHNetworkRequestType)requestType params:(NSDictionary *)params header:(NSDictionary *)header completionHandler:(void (^)(NSURLResponse *response,id responseObject,NSError *error))completionHandler; - (NSNumber *)dispatchTask:(NSURLSessionTask *)task; - (NSNumber *)uploadDataWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps params:(NSDictionary *)params contents:(NSArray*)contents header:(NSDictionary *)header progressHandler:(void(^)(NSProgress *))progressHandler completionHandler:(void (^)(NSURLResponse *response,id responseObject,NSError *error))completionHandler;- (void)cancelAllTask;- (void)cancelTaskWithTaskIdentifier:(NSNumber *)taskIdentifier; @end |
| 12345678 | @interface HHNetworkClient () @property (strong, nonatomic) AFHTTPSessionManager *sessionManager;@property (strong, nonatomic) NSMutableDictionary*dispathTable; @property (assign, nonatomic) CGFloat totalTaskCount;@property (assign, nonatomic) CGFloat errorTaskCount;@end |
1.請求的派發(fā)與取消
外部暴露數(shù)據(jù)請求和文件上傳的接口, 參數(shù)為構(gòu)建請求所需的必要參數(shù), 返回值為此次請求任務(wù)的taskIdentifier, 調(diào)用方可以通過taskIdentifier取消正在執(zhí)行的請求任務(wù).
內(nèi)部聲明一個dispathTable保持著此時正在執(zhí)行的任務(wù), 并在任務(wù)執(zhí)行完成或者任務(wù)取消時移除任務(wù)的引用, 以數(shù)據(jù)請求為例, 具體實現(xiàn)如下:
| 12345678910111213141516171819202122232425262728293031323334 | - (NSURLSessionDataTask *)dataTaskWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps requestType:(HHNetworkRequestType)requestType params:(NSDictionary *)params header:(NSDictionary *)header completionHandler:(void (^)(NSURLResponse *, id, NSError *))completionHandler { NSString *method = (requestType == HHNetworkRequestTypeGet ? @"GET" : @"POST"); NSMutableURLRequest *request = [[HHURLRequestGenerator sharedInstance] generateRequestWithUrlPath:urlPath useHttps:useHttps method:method params:params header:header]; NSMutableArray *taskIdentifier = [NSMutableArray arrayWithObject:@-1]; NSURLSessionDataTask *task = [self.sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); [self checkSeriveWithTaskError:error]; [self.dispathTable removeObjectForKey:taskIdentifier.firstObject]; dispatch_semaphore_signal(lock); completionHandler ? completionHandler(response, responseObject, error) : nil; }]; taskIdentifier[0] = @(task.taskIdentifier); return task;} - (NSNumber *)dispatchTaskWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps requestType:(HHNetworkRequestType)requestType params:(NSDictionary *)params header:(NSDictionary *)header completionHandler:(void (^)(NSURLResponse *, id, NSError *))completionHandler { return [self dispatchTask:[self dataTaskWithUrlPath:urlPath useHttps:useHttps requestType:requestType params:params header:header completionHandler:completionHandler]];} - (NSNumber *)dispatchTask:(NSURLSessionDataTask *)task { if (task == nil) { return @-1; } dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); self.totalTaskCount += 1; [self.dispathTable setObject:task forKey:@(task.taskIdentifier)]; dispatch_semaphore_signal(lock); [task resume]; return @(task.taskIdentifier);} |
代碼很簡單, 通過參數(shù)生成URLRequest, 然后通過AFHTTPSessionManager執(zhí)行任務(wù), 在任務(wù)執(zhí)行前我們以task.taskIdentifier為key保持一下執(zhí)行的任務(wù), 然后在任務(wù)執(zhí)行后我們移除這個任務(wù), 當(dāng)然, 外部也可以在必要的時候通過我們返回的task.taskIdentifier手動移除任務(wù).
注意我們先聲明一個NSMutableArray來標(biāo)志taskIdentifier, 然后在任務(wù)生成后設(shè)置taskIdentifier[0]為task. taskIdentifier, 最后在任務(wù)完成的回調(diào)block中使用taskIdentifier[0]來移除這個已經(jīng)完成的任務(wù).
可能有人會有疑問為什么不直接使用task.taskIdentifier, block不是可以捕獲task嗎? 下面解釋一下為什么這樣寫:
我們知道block之于函數(shù)最大的區(qū)別就在于它可以捕獲自身作用域外的對象, 并在block執(zhí)行的時候訪問被捕獲的對象, 具體的, 對于值類型對象block會生成一份此對象的拷貝, 對于引用類型對象block會生成一個此對象的引用并使該對象的引用計數(shù)+1(這里我們只描述非__block修飾的情況). 那么代入到上面的代碼, 我們來一步一步分析:
直接捕獲task的寫法
| 123456 | NSURLSessionDataTask *task = [self.sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { ...略 [self.dispathTable removeObjectForKey:@(task.taskIdentifier)];...略 }];[self.dispathTable setObject:task forKey:@(task.taskIdentifier)]; |
我們把它拆開來看:
| 12345678 | NSURLSessionDataTask *task; NSURLSessionDataTask *returnTask = [self.sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { ...略 [self.dispathTable removeObjectForKey:@(task.taskIdentifier)];...略 }];task = returnTask;[self.dispathTable setObject:task forKey:@(task.taskIdentifier)]; |
可以看到returnTask是我們實際存儲的任務(wù), 而task只是一個臨時變量, 此時task指向nil, 那我們生成returnTask的block此時捕獲到的task也就是nil, 所以在任務(wù)完成的時候我們的task.taskIdentifier一定是0, 這樣寫的結(jié)果就是dispathTable只會添加不會刪除(系統(tǒng)的taskIdentifier是從0開始依次遞增的), 當(dāng)然, 因為進(jìn)行中的returnTask我們是做了存儲的, 所以在任務(wù)未完成的時候我們還是可以做取消的.
如果一開始給task一個占位對象呢不讓它為nil可以嗎?
| 12345678 | NSURLSessionDataTask *task = [NSObject new]; //1.suspendNSURLSessionDataTask *returnTask = [self.sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { ...略 [self.dispathTable removeObjectForKey:@(task.taskIdentifier)];//3.completed...略 }];//2.alloctask = returnTask;[self.dispathTable setObject:task forKey:@(task.taskIdentifier)]; |
這樣其實就是一個簡單的引用變換題了, 我們來看看各個指針的指向情況:
| 123 | suspend: pTask->NSObject block.pTask->nil pReturnTask->nilalloc: pTask-> NSObject block.pTask->NSObject pReturnTask->returnTaskcompleted: pTask->returnTask block.pTask->NSObject pReturnTask->returnTask |
可以看到在任務(wù)執(zhí)行完成時我們訪問block.pTask時也不過是我們一開始的占位對象, 所以這個方案也不行, 當(dāng)然, 取消任務(wù)依然可用
事實上block.pTask確實是捕獲了占位對象, 只是我們在那之后沒有替換block.pTask指向到returnTask, 然而block.pTask我們是訪問不了的, 所以這個方案行不通.
如果我們的占位對象是一個容器呢?
| 12345678 | NSMutableArray *taskIdentifier = [NSMutableArray arrayWithObject:@-1];NSURLSessionDataTask *returnTask = [self.sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { ...略 [self.dispathTable removeObjectForKey:@(taskIdentifier.firstObject)];...略 }];taskIdentifier[0] = @(returnTask.taskIdentifier);[self.dispathTable setObject:task forKey:@(task.taskIdentifier)]; |
既然我們訪問不了block.pTask那就訪問block.pTask指向的對象嘛, 更改這個對象的內(nèi)容不就相當(dāng)于更改了block.pTask么, 大家照著2的思路走一下應(yīng)該很容易就能想通, 我就不多說了.
2.多服務(wù)器的切換
關(guān)于多服務(wù)器其實我也沒有實際的經(jīng)驗, 公司正在部署第二臺服務(wù)器, 具體需求是如果訪問第一臺服務(wù)器總是超時或者出錯, 那就切換到第二臺服務(wù)器, 基于此需求我簡單的實現(xiàn)一下:
| 12345678 | - (NSNumber *)dispatchTask:(NSURLSessionDataTask *)task { ...略 dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); self.totalTaskCount += 1; [self.dispathTable setObject:task forKey:@(task.taskIdentifier)]; dispatch_semaphore_signal(lock); ...略} |
| 1234567891011 | - (NSURLSessionDataTask *)dataTaskWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps requestType:(HHNetworkRequestType)requestType params:(NSDictionary *)params header:(NSDictionary *)header completionHandler:(void (^)(NSURLResponse *, id, NSError *))completionHandler { NSString *method = (requestType == HHNetworkRequestTypeGet ? @"GET" : @"POST"); ...略 NSURLSessionDataTask *task = [self.sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { ...略 [self checkSeriveWithTaskError:error]; ...略 }]; ...略} |
| 1234567891011121314151617181920 | - (void)checkSeriveWithTaskError:(NSError *)error { if ([HHAppContext sharedInstance].isReachable) { switch (error.code) { case NSURLErrorUnknown: case NSURLErrorTimedOut: case NSURLErrorCannotConnectToHost: { self.errorTaskCount += 1; } default:break; } if (self.totalTaskCount >= 40 && (self.errorTaskCount / self.totalTaskCount) == 0.1) { self.totalTaskCount = self.errorTaskCount = 0; [[HHURLRequestGenerator sharedInstance] switchService]; } }} |
| 1234567 | - (void)didReceivedSwitchSeriveNotification:(NSNotification *)notif { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); self.totalTaskCount = self.errorTaskCount = 0; dispatch_semaphore_signal(lock); [[HHURLRequestGenerator sharedInstance] switchToService:[notif.userInfo[@"service"] integerValue]];} |
假設(shè)認(rèn)為APP在此次使用過程中網(wǎng)絡(luò)任務(wù)的錯誤率達(dá)到10%那就應(yīng)該切換一下服務(wù)器, 我們在任務(wù)派發(fā)前將任務(wù)總數(shù)+1, 然后在任務(wù)結(jié)束后判斷任務(wù)是否成功, 失敗的話將任務(wù)失敗總數(shù)+1再判斷是否到達(dá)最大錯誤率, 進(jìn)而切換到另一臺服務(wù)器.
另外還有一種情況是大部分服務(wù)器都掛了, 后臺直接走APNS推送可用的服務(wù)器序號過來, 就不用挨個挨個切換了.
三、合理的使用請求派發(fā)器
OK, 炮彈有了, 炮臺也就緒了, 接下來看看如何使用這個炮臺.
| 1234567891011121314151617181920212223242526272829303132333435363738394041 | #pragma mark - HHAPIConfiguration typedef void(^HHNetworkTaskProgressHandler)(CGFloat progress);typedef void(^HHNetworkTaskCompletionHander)(NSError *error, id result); @interface HHAPIConfiguration : NSObject @property (copy, nonatomic) NSString *urlPath;@property (strong, nonatomic) NSDictionary *requestParameters; @property (assign, nonatomic) BOOL useHttps;@property (strong, nonatomic) NSDictionary *requestHeader;@property (assign, nonatomic) HHNetworkRequestType requestType;@end @interface HHDataAPIConfiguration : HHAPIConfiguration @property (assign, nonatomic) NSTimeInterval cacheValidTimeInterval; @end @interface HHUploadAPIConfiguration : HHAPIConfiguration @property (strong, nonatomic) NSArray* uploadContents; @end #pragma mark - HHAPIManager @interface HHAPIManager : NSObject - (void)cancelAllTask;- (void)cancelTaskWithtaskIdentifier:(NSNumber *)taskIdentifier;+ (void)cancelTaskWithtaskIdentifier:(NSNumber *)taskIdentifier;+ (void)cancelTasksWithtaskIdentifiers:(NSArray *)taskIdentifiers; - (NSURLSessionDataTask *)dataTaskWithConfiguration:(HHDataAPIConfiguration *)config completionHandler:(HHNetworkTaskCompletionHander)completionHandler;- (NSNumber *)dispatchDataTaskWithConfiguration:(HHDataAPIConfiguration *)config completionHandler:(HHNetworkTaskCompletionHander)completionHandler;- (NSNumber *)dispatchUploadTaskWithConfiguration:(HHUploadAPIConfiguration *)config progressHandler:(HHNetworkTaskProgressHandler)progressHandler completionHandler:(HHNetworkTaskCompletionHander)completionHandler; @end |
| 12345678910111213141516171819202122232425262728293031 | - (void)cancelAllTask { for (NSNumber *taskIdentifier in self.loadingTaskIdentifies) { [[HHNetworkClient sharedInstance] cancelTaskWithTaskIdentifier:taskIdentifier]; } [self.loadingTaskIdentifies removeAllObjects];} - (void)cancelTaskWithtaskIdentifier:(NSNumber *)taskIdentifier { [[HHNetworkClient sharedInstance] cancelTaskWithTaskIdentifier:taskIdentifier]; [self.loadingTaskIdentifies removeObject:taskIdentifier];} + (void)cancelTaskWithtaskIdentifier:(NSNumber *)taskIdentifier { [[HHNetworkClient sharedInstance] cancelTaskWithTaskIdentifier:taskIdentifier];} + (void)cancelTasksWithtaskIdentifiers:(NSArray *)taskIdentifiers { for (NSNumber *taskIdentifier in taskIdentifiers) { [[HHNetworkClient sharedInstance] cancelTaskWithTaskIdentifier:taskIdentifier]; }} - (NSURLSessionDataTask *)dataTaskWithConfiguration:(HHDataAPIConfiguration *)config completionHandler:(HHNetworkTaskCompletionHander)completionHandler { return [[HHNetworkClient sharedInstance] dataTaskWithUrlPath:config.urlPath useHttps:config.useHttps requestType:config.requestType params:config.requestParameters header:config.requestHeader completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { completionHandler ? completionHandler([self formatError:error], responseObject) : nil; }];} |
HHAPIManager對外提供數(shù)據(jù)請求和取消的接口, 內(nèi)部調(diào)用HHNetworkClient進(jìn)行實際的請求操作.
1.協(xié)議還是配置對象?
HHAPIManager的接口我們并沒有像之前一樣提供多個參數(shù), 而是將多個參數(shù)組合為一個配置對象, 下面說一下為什么這樣做:
為什么多個參數(shù)的接口方式不好?
一個APP中調(diào)用的API通常都是數(shù)以百計甚至千計, 如果有一天需要對已成型的所有的API都追加一個參數(shù), 此時的改動之多, 足使男程序員沉默, 女程序員流淚.
舉個例子: APP1.0已經(jīng)上線, 1.1版本總監(jiān)突然要求對數(shù)據(jù)請求加上緩存, 操作請求不用加緩存, 如果是參數(shù)接口的形式一般就是這樣寫:
| 12345678910111213 | //老接口- (NSNumber *)dispatchTaskWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps method:(NSString *)method params:(NSDictionary *)params header:(NSDictionary *)header;//新接口- (NSNumber *)dispatchTaskWithUrlPath:(NSString *)urlPath useHttps:(BOOL)useHttps method:(NSString *)method params:(NSDictionary *)params header:(NSDictionary *)header shouldCache:(BOOL)shouldCache; |
然后原來的老接口全都調(diào)用新接口shouldCache默認(rèn)傳NO, 不需要緩存的API不用做改動, 而需要緩存的API都得改調(diào)用新接口然后shouldCache傳YES.
這樣能暫時解決問題, 工作量也會小一些, 然后過了兩天總監(jiān)過來說, 為什么沒有對API區(qū)分緩存時間? 還有, 我們又有新需求了. 呵呵!
使用協(xié)議提升拓展性
| 12345678910111213 | @protocol HHAPIManager @required- (BOOL)useHttps;- (NSString *)urlPath;- (NSDictionary *)parameters;- (OTSNetworkRequestType)requestType; @optional- (BOOL)checkParametersIsValid;- (NSTimeInterval)cacheValidTimeInterval;- (NSArray*)uploadContents;@end |
| 12345 | @interface HHAPIManager : NSObject...略- (NSNumber *)dispatchTaskWithCompletionHandler:(OTSNetworkTaskCompletionHander)completionHandler;...略@end |
其實最初的設(shè)計是走協(xié)議的, HHAPIManager遵守這個協(xié)議, 內(nèi)部給上默認(rèn)參數(shù), dispatchTaskWithCompletionHandler:會去挨個獲取這些參數(shù), 各個子類自行實現(xiàn)自己自定義的部分, 這樣以后就算有任何拓展, 只需要在協(xié)議里面加個方法基類給上默認(rèn)值, 有需要的子類API重寫一下就行了.
替換協(xié)議為配置對象
| 123 | - (NSURLSessionDataTask *)dataTaskWithConfiguration:(HHDataAPIConfiguration *)config completionHandler:(HHNetworkTaskCompletionHander)completionHandler;- (NSNumber *)dispatchDataTaskWithConfiguration:(HHDataAPIConfiguration *)config completionHandler:(HHNetworkTaskCompletionHander)completionHandler;- (NSNumber *)dispatchUploadTaskWithConfiguration:(HHUploadAPIConfiguration *)config progressHandler:(HHNetworkTaskProgressHandler)progressHandler completionHandler:(HHNetworkTaskCompletionHander)completionHandler; |
協(xié)議的方案其實很好, 也是我想要的設(shè)計. 但是協(xié)議是針對類而言的, 這意味著今后的每添加一個API就需要新建一個HHAPIManager的子類, 很容易就有了幾百個API類文件, 維護(hù)起來很麻煩, 找起來很麻煩(以上是同事要求替換協(xié)議的理由, 我仍然支持協(xié)議, 但是他們?nèi)硕?. 所以將協(xié)議替換為配置對象, 然后API以模塊功能劃分, 每個模塊一個類文件給出多個API接口 ,內(nèi)部每個API搭上合適的配置對象, 這樣一來只需要十幾個類文件.
總之, 考慮到配置對象既可以實現(xiàn)單個API單個類的設(shè)計, 也可以滿足同事的需求, 協(xié)議被換成了配置對象.
另外, 所有的block參數(shù)都不寫在配置對象里, 而是直接在接口處聲明, 看著別扭寫著方便(block做參數(shù)和做屬性哪個寫起來簡單大家都懂的).
2.簡單的請求結(jié)果緩存器
上面簡單提到了請求緩存, 其實我們是沒有做緩存的, 因為我司HTTP的API現(xiàn)在基本上都被廢棄了, 全是走TCP, 然而TCP的緩存又是另一個故事了.但是還是簡單實現(xiàn)一下吧:
| 123456789101112131415161718192021 | #define HHCacheManager [HHNetworkCacheManager sharedManager] @interface HHNetworkCache : NSObject + (instancetype)cacheWithData:(id)data;+ (instancetype)cacheWithData:(id)data validTimeInterval:(NSUInteger)interterval; - (id)data;- (BOOL)isValid; @end @interface HHNetworkCacheManager : NSObject + (instancetype)sharedManager; - (void)removeObejectForKey:(id)key;- (void)setObjcet:(HHNetworkCache *)object forKey:(id)key;- (HHNetworkCache *)objcetForKey:(id)key; @end |
| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 | #define ValidTimeInterval 60 @implementation HHNetworkCache + (instancetype)cacheWithData:(id)data { return [self cacheWithData:data validTimeInterval:ValidTimeInterval];} + (instancetype)cacheWithData:(id)data validTimeInterval:(NSUInteger)interterval { HHNetworkCache *cache = [HHNetworkCache new]; cache.data = data; cache.cacheTime = [[NSDate date] timeIntervalSince1970]; cache.validTimeInterval = interterval > 0 ? interterval : ValidTimeInterval; return cache;} - (BOOL)isValid { if (self.data) { return [[NSDate date] timeIntervalSince1970] - self.cacheTime < self.validTimeInterval; } return NO;} @end #pragma mark - HHNetworkCacheManager @interface HHNetworkCacheManager () @property (strong, nonatomic) NSCache *cache; @end @implementation HHNetworkCacheManager + (instancetype)sharedManager { static HHNetworkCacheManager *sharedManager; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedManager = [[super allocWithZone:NULL] init]; [sharedManager configuration]; }); return sharedManager;} + (instancetype)allocWithZone:(struct _NSZone *)zone { return [self sharedManager];} - (void)configuration { self.cache = [NSCache new]; self.cache.totalCostLimit = 1024 * 1024 * 20;} #pragma mark - Interface - (void)setObjcet:(HHNetworkCache *)object forKey:(id)key { [self.cache setObject:object forKey:key];} - (void)removeObejectForKey:(id)key { [self.cache removeObjectForKey:key];} - (HHNetworkCache *)objcetForKey:(id)key { return [self.cache objectForKey:key];} @end |
| 12345678910111213141516171819202122232425262728293031323334 | - (NSNumber *)dispatchDataTaskWithConfiguration:(HHDataAPIConfiguration *)config completionHandler:(HHNetworkTaskCompletionHander)completionHandler{ NSString *cacheKey; if (config.cacheValidTimeInterval > 0) { NSMutableString *mString = [NSMutableString stringWithString:config.urlPath]; [config.requestParameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [mString appendFormat:@"&%@=%@",key, obj]; }]; cacheKey = [self md5WithString:[mString copy]]; HHNetworkCache *cache = [HHCacheManager objcetForKey:cacheKey]; if (!cache.isValid) { [HHCacheManager removeObejectForKey:cacheKey]; } else { completionHandler ? completionHandler(nil, cache.data) : nil; return @-1; } } NSMutableArray *taskIdentifier = [NSMutableArray arrayWithObject:@-1]; taskIdentifier[0] = [[HHNetworkClient sharedInstance] dispatchTaskWithUrlPath:config.urlPath useHttps:config.useHttps requestType:config.requestType params:config.requestParameters header:config.requestHeader completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { if (!error && config.cacheValidTimeInterval > 0) { HHNetworkCache *cache = [HHNetworkCache cacheWithData:responseObject validTimeInterval:config.cacheValidTimeInterval]; [HHCacheManager setObjcet:cache forKey:cacheKey]; } [self.loadingTaskIdentifies removeObject:taskIdentifier.firstObject]; completionHandler ? completionHandler([self formatError:error], responseObject) : nil; }]; [self.loadingTaskIdentifies addObject:taskIdentifier.firstObject]; return taskIdentifier.firstObject; |
簡單定義一個HHCache對象, 存放緩存數(shù)據(jù), 緩存時間, 緩存時效, 然后HHNetworkCacheManager單例對象內(nèi)部用NSCache存儲緩存對象, 因為NSCache自帶線程安全特效, 連鎖都不用.
在任務(wù)發(fā)起之前我們檢查一下是否有可用緩存, 有可用緩存直接返回, 沒有就走網(wǎng)絡(luò), 網(wǎng)絡(luò)任務(wù)成功后存一下請求數(shù)據(jù)即可.
3.請求結(jié)果的格式化
網(wǎng)絡(luò)任務(wù)完成后帶回的數(shù)據(jù)以什么樣的形式返回給調(diào)用方, 分兩種情況: 任務(wù)成功和任務(wù)失敗.這里我們定義一下任務(wù)成功和失敗, 成功表示網(wǎng)絡(luò)請求成功且?guī)Щ亓丝捎脭?shù)據(jù), 失敗表示未獲取到可用數(shù)據(jù).
舉個例子: 獲取一個話題列表, 用戶希望看到的看到是一排排彩色頭像, 如果你調(diào)用API拿不到這一堆數(shù)據(jù)那對于用戶來說就是失敗的. 那么沒拿到數(shù)據(jù)可能是網(wǎng)絡(luò)出錯了, 或者網(wǎng)絡(luò)沒有問題只是用戶沒有關(guān)注過任何話題, 那么相應(yīng)的展示網(wǎng)絡(luò)錯誤提示或者推薦話題提示.
任務(wù)成功的話很簡單, 直接做相應(yīng)JSON解析正常返回就行, 如果某個XXXAPI有特殊需求那就新加一個XXXAPIConfig繼承APIConfig基類, 在里面添加屬性或者方法描述一下你有什么特殊需求, XXXAPI負(fù)責(zé)格式好返回就行了(所以還是一個API一個類好, 干凈).
任務(wù)失敗的話就麻煩一點, 我希望任何API都能友好的返回錯誤提示, 具體的, 如果有錯誤發(fā)生了, 那么返回給調(diào)用方的error.code一定是可讀的枚舉而不是301之類的需要比對文檔的錯誤碼(必須), error.domain通常就是錯誤提示語(可選), 這就要求程序員寫每個API時都定義好錯誤枚舉(所以還是一個API一個類好, 干凈)和相應(yīng)的錯誤提示.大概是這樣子:
| 12345678910111213141516171819 | //HHNetworkTaskError.h 通用錯誤typedef enum : NSUInteger { HHNetworkTaskErrorTimeOut = 101, HHNetworkTaskErrorCannotConnectedToInternet = 102, HHNetworkTaskErrorCanceled = 103, HHNetworkTaskErrorDefault = 104, HHNetworkTaskErrorNoData = 105, HHNetworkTaskErrorNoMoreData = 106} HHNetworkTaskError; static NSError *HHError(NSString *domain, int code) { return [NSError errorWithDomain:domain code:code userInfo:nil];} static NSString *HHNoDataErrorNotice = @"這里什么也沒有~";static NSString *HHNetworkErrorNotice = @"當(dāng)前網(wǎng)絡(luò)差, 請檢查網(wǎng)絡(luò)設(shè)置~";static NSString *HHTimeoutErrorNotice = @"請求超時了~";static NSString *HHDefaultErrorNotice = @"請求失敗了~";static NSString *HHNoMoreDataErrorNotice = @"沒有更多了~"; |
| 12345678910111213141516171819202122232425 | - (NSError *)formatError:(NSError *)error { if (error != nil) { switch (error.code) { case NSURLErrorCancelled: { error = HHError(HHDefaultErrorNotice, HHNetworkTaskErrorCanceled); } break; case NSURLErrorTimedOut: { error = HHError(HHTimeoutErrorNotice, HHNetworkTaskErrorTimeOut); } case NSURLErrorCannotFindHost: case NSURLErrorCannotConnectToHost: case NSURLErrorNotConnectedToInternet: {//應(yīng)產(chǎn)品要求, 所有連不上服務(wù)器都是用戶網(wǎng)絡(luò)的問題 error = HHError(HHNetworkErrorNotice, HHNetworkTaskErrorCannotConnectedToInternet); } default: { error = HHError(HHNoDataErrorNotice, HHNetworkTaskErrorDefault); } break; } } return error;} |
通用的錯誤枚舉和提示語定義在一個.h中, 以后有新增通用描述都在這里添加, 便于管理. HHAPIManager基類會先格式好某些通用錯誤, 然后各個子類定義自己特有的錯誤枚舉(不可和通用描述沖突)和錯誤描述, 像這樣:
| 123456789101112 | //HHTopicAPIManager.htypedef enum : NSUInteger { HHUserInfoTaskErrorNotExistUserId = 1001,//用戶不存在 HHUserInfoTaskError1,//瞎寫的, 意思到就行 HHUserInfoTaskError2} HHUserInfoTaskError; typedef enum : NSUInteger { HHUserFriendListTaskError0 = 1001, HHUserFriendListTaskError1, HHUserFriendListTaskError2,} HHTopicListTaskError; |
| 12345678910111213141516171819202122232425262728293031323334 | //HHTopicAPIManager.m- (NSNumber *)fetchUserInfoWithUserId:(NSUInteger)userId completionHandler:(HHNetworkTaskCompletionHander)completionHandler { HHDataAPIConfiguration *config = [HHDataAPIConfiguration new]; config.urlPath = @"fetchUserInfoWithUserIdPath"; config.requestParameters = nil; return [super dispatchDataTaskWithConfiguration:config completionHandler:^(NSError *error, id result) { if (!error) {//通用錯誤基類已經(jīng)處理好, 做好自己的數(shù)據(jù)格式就行 switch ([result[@"code"] integerValue]) { case 200: { // 請求數(shù)據(jù)無誤做相應(yīng)解析 // result = [HHUser objectWithKeyValues:result[@"data"]]; } break; case 301: { error = HHError(@"用戶不存在", HHUserInfoTaskErrorNotExistUserId); } break; case 302: { error = HHError(@"xxx錯誤", HHUserInfoTaskError1); } break; case 303: { error = HHError(@"yyy錯誤", HHUserInfoTaskError2); } break; default:break; } } completionHandler ? completionHandler(error, result) : nil; }];} |
然后調(diào)用方一般情況下只需要這樣:
| 123 | [[HHTopicAPIManager new] fetchUserInfoWithUserId:123 completionHandler:^(NSError *error, id result) { error ? [self showToastWithText:error.domain] : [self reloadTableViewWithNames:result]; }]; |
當(dāng)然, 情況復(fù)雜的話只能這樣, 代碼多一點, 但是有枚舉讀起來也不麻煩:
| 1234567891011121314151617181920 | [[HHTopicAPIManager new] fetchUserInfoWithUserId:123 completionHandler:^(NSError *error, id result) { error ? [self showErrorViewWithError:error] : [self reloadTableViewWithNames:result]; }]; - (void)showErrorViewWithError:(NSError *)error { switch (error.code) {//如果情況復(fù)雜就自己switch case HHNetworkTaskErrorTimeOut: { // 展示請求超時錯誤頁面 } break; case HHNetworkTaskErrorCannotConnectedToInternet: { // 展示網(wǎng)絡(luò)錯誤頁面 } case HHUserInfoTaskErrorNotExistUserId: { // ... } // ... default:break; }} |
這里多扯兩句, 請求的回調(diào)我是以(error, id)的形式返回的, 而不是像AFN那樣分別給出successBlock和failBlock. 其實我本身是很支持AFN的做法的, 區(qū)分成功和錯誤強(qiáng)行讓兩種業(yè)務(wù)的代碼出現(xiàn)在兩個不同的部分, 這很好, 不同的業(yè)務(wù)處理就該在不同函數(shù)/方法里面. 但是實際開發(fā)中有很多成功和失敗都會執(zhí)行的操作, 典型的例子就是HUD, 兩個block的話我需要在兩個地方都加上[HUD hide], 這樣的代碼寫的多了就會很煩, 而我又懶, 所以就成功失敗都在一個回調(diào)返回了.
但是! 你也應(yīng)該區(qū)分不同的業(yè)務(wù)寫出兩個不同方法(像上面那樣做), 至于公用的部分就只寫一次就夠了.像這樣:
| 12345 | [hud show:YES];[[HHTopicAPIManager new] fetchUserInfoWithUserId:123 completionHandler:^(NSError *error, id result) { [hud hide:YES]; error ? [self showToastWithText:error.domain] : [self reloadTableViewWithNames:result]; }]; |
再說一句, 即使你比我還懶, 不聲明兩個方法那也應(yīng)該將較短的邏輯寫在前面, 較長的寫在后面, 易讀, 像這樣:
| 1234567891011121314151617181920 | if (!error) { ...短 ...短 } else { switch (error.code) {//如果情況復(fù)雜就自己switch case HHNetworkTaskErrorTimeOut: { // 展示請求超時錯誤頁面 } break; case HHNetworkTaskErrorCannotConnectedToInternet: { // 展示網(wǎng)絡(luò)錯誤頁面 } case HHUserInfoTaskErrorNotExistUserId: { // ...長 } // ...長 default:break; } } } |
4.兩個小玩意兒
文章到這基本上這個網(wǎng)絡(luò)層該說的都說的差不多了, 各位可以根據(jù)自己的需求改動改動就能用了, 最后簡單介紹下兩個和它相關(guān)的小玩意兒就結(jié)尾吧:
HHNetworkTaskGroup
| 12345678910111213141516 | @protocol HHNetworkTask - (void)cancel;- (void)resume; @end @interface HHNetworkTaskGroup : NSObject - (void)addTaskWithMessgeType:(NSInteger)type message:(id)message completionHandler:(HHNetworkTaskCompletionHander)completionHandler;- (void)addTask:(id)task; - (void)cancel;- (void)dispatchWithNotifHandler:(void(^)(void))notifHandler; @end |
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 | @interface HHNetworkTaskGroup () @property (copy, nonatomic) void(^notifHandler)(void);@property (assign, nonatomic) NSInteger signal;@property (strong, nonatomic) NSMutableSet *tasks;@property (strong, nonatomic) dispatch_semaphore_t lock; @property (strong, nonatomic) id keeper; @end @implementation HHNetworkTaskGroup //- (void)addTaskWithMessgeType:(HHSocketMessageType)type message:(PBGeneratedMessage *)message completionHandler:(HHNetworkCompletionHandler)completionHandler {// // HHSocketTask *task = [[HHSocketManager sharedManager] taskWithMessgeType:type message:message completionHandler:completionHandler];// [self addTask:task];//} - (void)addTask:(id)task { if ([task respondsToSelector:@selector(cancel)] && [task respondsToSelector:@selector(resume)] && ![self.tasks containsObject:task]) { [self.tasks addObject:task]; [(id)task addObserver:self forKeyPath:NSStringFromSelector(@selector(state)) options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil]; }} - (void)dispatchWithNotifHandler:(void (^)(void))notifHandler { if (self.tasks.count == 0) { dispatch_async(dispatch_get_main_queue(), ^{ notifHandler ? notifHandler() : nil; }); return; } self.lock = dispatch_semaphore_create(1); self.keeper = self; self.signal = self.tasks.count; self.notifHandler = notifHandler; for (idtask in self.tasks.allObjects) { [task resume]; }} - (void)cancel { for (idtask in self.tasks.allObjects) { if ([(id)task state] < NSURLSessionTaskStateCanceling) { [(id)task removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))]; [task cancel]; } } [self.tasks removeAllObjects]; self.keeper = nil;} #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context { if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) { NSURLSessionTaskState oldState = [change[NSKeyValueChangeOldKey] integerValue]; NSURLSessionTaskState newState = [change[NSKeyValueChangeNewKey] integerValue]; if (oldState != newState && newState >= NSURLSessionTaskStateCanceling) { [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))]; dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER); self.signal--; dispatch_semaphore_signal(self.lock); if (self.signal == 0) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.notifHandler ? self.notifHandler() : nil; [self.tasks removeAllObjects]; self.keeper = nil; }); } } }} #pragma mark - Getter - (NSMutableSet *)tasks { if (!_tasks) { _tasks = [NSMutableSet set]; } return _tasks;} @end |
看名字應(yīng)該就知道這個是和dispatch_group_notif差不多的東西, 不過是派發(fā)的對象不是dispatch_block_t而是id. 代碼很簡單, 說說思路就行了.
keeper
系統(tǒng)大部分帶有Block的API都有一個特性就是只需要生成不需要持有, 也不用擔(dān)心Block持有我們的對象而造成循環(huán)引用, 例如:dispatch_async, dataTaskWithURL:completionHandler:等等, 其實具體的實現(xiàn)就是先循環(huán)引用再破除循環(huán)引用, 比如dispatch_async的queue和block會循環(huán)引用, 這樣在block執(zhí)行期間雙方都不會釋放, 然后等到block執(zhí)行完成后再將queue.block置nil破除循環(huán)引用, block沒了, 那它捕獲的queue和其他對象計數(shù)都能-1,也就都能正常釋放了.代碼里面的keeper就是來制造這個循環(huán)引用的.
signal和tasks
signal其實就是tasks.count, 為什么我們不直接在task完成后直接tasks.remove然后判斷tasks.count == 0而是要間接給一個signal來做這事兒?
原因很簡單: forin過程中是不能改變?nèi)萜鲗ο蟮? 當(dāng)我們forin派發(fā)task的時候, task是異步執(zhí)行的, 有可能在task執(zhí)行完成觸發(fā)KVO的時候我們的forin還在遍歷, 此時直接remove就會crash. 如果不用forin, 而是用while或者for(;;)就會漏發(fā). 所以就聲明一個signal來做計數(shù)了. 另外addObserve和removeObserve必須成對出現(xiàn), 控制好就行.
dispatch_after
在所有任務(wù)執(zhí)行完成后并沒有馬上執(zhí)行notif(), 而是等待0.1秒以后再執(zhí)行notif(), 這是因為task.state的設(shè)置會在task.completionHandler之前執(zhí)行, 所以我們需要等一下, 確認(rèn)completionHandler執(zhí)行后在走我們的notif().
如何使用
| 12345678910111213 | HHNetworkTaskGroup *group = [HHNetworkTaskGroup new]; HHTopicAPIManager *manager = [HHTopicAPIManager new]; for (int i = 1; i < 6; i++) { NSURLSessionDataTask *task = [manager topicListDataTaskWithPage:i pageSize:20 completionHandler:^(NSError *error, id result) { //...completionHandler... i }]; [group addTask:(id)task]; } [group dispatchWithNotifHandler:^{ //notifHandler }]; |
強(qiáng)調(diào)一下, 絕對不應(yīng)該直接調(diào)用HHNetworkClient或者HHAPIManger的dataTaskxxx…這些通用接口來生成task, 應(yīng)該在該task所屬的API暴露接口生成task, 簡單說就是不要跨層訪問. 每個API的參數(shù)甚至簽名規(guī)則都是不一樣的, API的調(diào)用方應(yīng)該只提供生成task的相應(yīng)參數(shù)而不應(yīng)該也不需要知道這些參數(shù)具體的拼裝邏輯.
HHNetworkAPIRecorder
| 123456789101112 | @interface HHNetworkAPIRecorder : NSObject @property (strong, nonatomic) id rawValue;@property (assign, nonatomic) int pageSize;@property (assign, nonatomic) int currentPage;@property (assign, nonatomic) NSInteger itemsCount;@property (assign, nonatomic) NSInteger lastRequestTime; - (void)reset;- (BOOL)hasMoreData;- (NSInteger)maxPage;@end |
日常請求中有很多接口涉及到分頁, 然而毫無疑問分頁的邏輯在每個頁面都是一模一樣的, 但是卻需要每個調(diào)用頁面都保持一下currentPage然后調(diào)用邏輯都寫一次, 其實直接在API內(nèi)部實現(xiàn)一下分頁的邏輯, 然后對外暴露第一頁和下一頁的接口就不用聲明currentPage和重復(fù)這些無聊的邏輯了. 像這樣:
| 1234 | //XXXAPI.h- (NSNumber *)refreshTopicListWithCompletionHandler:(HHNetworkTaskCompletionHander)completionHandler;//第一頁- (NSNumber *)loadmoreTopicListWithCompletionHandler:(HHNetworkTaskCompletionHander)completionHandler;//當(dāng)前頁的下一頁- (NSNumber *)fetchTopicListWithPage:(NSInteger)page completionHandler:(HHNetworkTaskCompletionHander)completionHandler;//指定頁(一般外部用不到, 看情況暴露) |
| 123456789101112 | //XXXAPI.m- (NSNumber *)refreshTopicListWithCompletionHandler:(HHNetworkTaskCompletionHander)completionHandler { [self.topicListAPIRecorder reset]; return [self fetchTopicListWithPage:self.topicListAPIRecorder.currentPage completionHandler:completionHandler];} - (NSNumber *)loadmoreTopicListWithCompletionHandler:(HHNetworkTaskCompletionHander)completionHandler { self.topicListAPIRecorder.currentPage++; return [self fetchTopicListWithPage:self.topicListAPIRecorder.currentPage completionHandler:completionHandler];} |
| 12345678910111213 | //SomeViewControllerself.topicAPIManager = [HHTopicAPIManager new];...self.tableView.header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{//下拉刷新 [weakSelf.topicAPIManager refreshTopicListWithCompletionHandler:^(NSError *error, id result) { ... }]; }];self.tableView.footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{//上拉加載 [weakSelf.topicAPIManager loadmoreTopicListWithCompletionHandler:^(NSError *error, id result) { ... }]; }]; |
總結(jié)
HHURLRequestGenerator: 網(wǎng)絡(luò)請求的生成器, 公用的請求頭, cookie都在此設(shè)置.
HHNetworkClient: 網(wǎng)絡(luò)請求的派發(fā)器, 這里會記錄每一個服役中的請求, 并在必要的時候切換服務(wù)器.
HHAPIManager: 網(wǎng)絡(luò)請求派發(fā)器的調(diào)用者, 這里對請求的結(jié)果做相應(yīng)的數(shù)據(jù)格式化后返回給API調(diào)用方, 提供請求模塊的拓展性支持, 并提供合理的Task供TaskGroup派發(fā).
本文附帶的demo地址:https://github.com/HeiHuaBaiHua/TAFNetworking
</div>新聞熱點
疑難解答
圖片精選