前言
本文參考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];    }];    returnrequest;} | 
| 12345678910111213141516 | - (NSString *)urlStringWithPath:(NSString *)path useHttps:(BOOL)useHttps {        if([path hasprefix:@"http"]) {        returnpath;    } 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 - Interfacestatic 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];    });       returncurrentService;}+ (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) {        caseHHService0: service = [HHServiceX new];  break;        caseHHService1: service = [HHServiceY new];  break;        caseHHService2: service = [HHServiceZ new];  break;    }    service.type = type;    service.environment = BulidServiceEnvironment;    returnservice;}- (NSString *)baseUrl {        switch(self.environment) {        caseHHServiceEnvironmentTest: return[self testEnvironmentBaseUrl];        caseHHServiceEnvironmentDevelop: return[self developEnvironmentBaseUrl];        caseHHServiceEnvironmentRelease: 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);    returntask;}- (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) {                            caseNSURLErrorUnknown:            caseNSURLErrorTimedOut:            caseNSURLErrorCannotConnectToHost: {                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 - HHAPIConfigurationtypedef 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 inself.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 intaskIdentifiers) {        [[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;    returncache;}- (BOOL)isValid {        if(self.data) {        return[[NSDate date] timeIntervalSince1970] - self.cacheTime < self.validTimeInterval;    }    returnNO;}@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 = [[superallocWithZone:NULL] init];        [sharedManager configuration];    });    returnsharedManager;}+ (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];    returntaskIdentifier.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) {            caseNSURLErrorCancelled: {                error = HHError(HHDefaultErrorNotice, HHNetworkTaskErrorCanceled);            }   break;                            caseNSURLErrorTimedOut: {                error = HHError(HHTimeoutErrorNotice, HHNetworkTaskErrorTimeOut);            }                           caseNSURLErrorCannotFindHost:            caseNSURLErrorCannotConnectToHost:            caseNSURLErrorNotConnectedToInternet: {//應(yīng)產(chǎn)品要求, 所有連不上服務(wù)器都是用戶網(wǎng)絡(luò)的問題                error = HHError(HHNetworkErrorNotice, HHNetworkTaskErrorCannotConnectedToInternet);            }                            default: {                error = HHError(HHNoDataErrorNotice, HHNetworkTaskErrorDefault);            }   break;        }    }    returnerror;} | 
通用的錯誤枚舉和提示語定義在一個.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[superdispatchDataTaskWithConfiguration:config completionHandler:^(NSError *error, id result) {                if(!error) {//通用錯誤基類已經(jīng)處理好, 做好自己的數(shù)據(jù)格式就行                        switch([result[@"code"] integerValue]) {                case200: {                    //                    請求數(shù)據(jù)無誤做相應(yīng)解析                    //                    result = [HHUser objectWithKeyValues:result[@"data"]];                }   break;                                    case301: {                    error = HHError(@"用戶不存在", HHUserInfoTaskErrorNotExistUserId);                }  break;                                   case302: {                    error = HHError(@"xxx錯誤", HHUserInfoTaskError1);                }   break;                                    case303: {                    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                caseHHNetworkTaskErrorTimeOut: {                    //                    展示請求超時錯誤頁面                }   break;                caseHHNetworkTaskErrorCannotConnectedToInternet: {                    //                    展示網(wǎng)絡(luò)錯誤頁面                }                caseHHUserInfoTaskErrorNotExistUserId: {                    //                    ...                }                    //                    ...                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                caseHHNetworkTaskErrorTimeOut: {                    //                    展示請求超時錯誤頁面                }   break;                caseHHNetworkTaskErrorCannotConnectedToInternet: {                    //                    展示網(wǎng)絡(luò)錯誤頁面                }                caseHHUserInfoTaskErrorNotExistUserId: {                    //                    ...長                }                    //                    ...長                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 inself.tasks.allObjects) {        [task resume];    }}- (void)cancel {        for(idtask inself.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>新聞熱點
疑難解答
圖片精選