国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > iOS > 正文

iOS 如何一步一步搭建框架

2019-11-07 23:40:41
字體:
供稿:網(wǎng)友

前言

本文參考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的寫法

123456NSURLSessionDataTask *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)];

我們把它拆開來看: 

12345678NSURLSessionDataTask *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可以嗎?

12345678NSURLSessionDataTask *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)];

這樣其實就是一個簡單的引用變換題了, 我們來看看各個指針的指向情況:

123suspend: 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我們是訪問不了的, 所以這個方案行不通.

如果我們的占位對象是一個容器呢?

12345678NSMutableArray *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)該將較短的邏輯寫在前面, 較長的寫在后面, 易讀, 像這樣: 

1234567891011121314151617181920if (!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>
上一篇:iOS 定位

下一篇:AudioSession詳解

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 内黄县| 长子县| 游戏| 盐边县| 招远市| 临西县| 大理市| 兴仁县| 连州市| 乐都县| 额尔古纳市| 台前县| 黄骅市| 山西省| 湖北省| 沂南县| 图木舒克市| 台东市| 南通市| 舒兰市| 高邑县| 漯河市| 罗甸县| 珲春市| 夏河县| 绵竹市| 公安县| 大邑县| 元朗区| 舟曲县| 根河市| 高密市| 九寨沟县| 永城市| 三亚市| 毕节市| 马公市| 隆昌县| 东至县| 体育| 会宁县|