本框架實現思路與YTKNetwork和RTNetworking類似,相當於一個簡單版,把每一個網路請求封裝成物件。使用LXNetwork,你的每一個請求都需要繼承LXBaseRequest類,通過覆蓋父類的一些方法或者實現相關協議方法來構造指定的網路請求。這個網路庫可直接在專案中使用,但是有些功能完成度不是很完美,待完善。
GitHud地址:https://github.com/CoderLXWang/LXNetwork
一、為什麼要這樣做?
實現思路的圖在下面,可以對比著圖看下面內容。
直接封裝一個簡易的HttpTool,裡面直接呼叫AF,返回responseObject直接返回, 這樣不行嗎, 為什麼要弄這麼麻煩?
顯而易見的優點大概有以下幾點:
1,前後隔離AFNetworking,以後如果升級AF或者替換其他框架, 只需要改動直接與AF接觸的LXRequestProxy和LXResponse內的程式碼即可,避免對專案中業務程式碼產生影響(半小時完成從AF2.6升級AF3.0,重度使用的三方框架一般都要隔離一下)
2,將每個介面抽象成一個類,易於管理,按每個介面的需求構造請求(比如有的介面要快取,有的介面不要快取)
3,所有介面呼叫都經過LXBaseRequest,可以方便的在基類中處理公共邏輯(比如專案全部完成了,突然要用請求引數排序,加鹽等方式加密)
缺點:使用麻煩。。。。。
二、思路講解
包括快取在內的大體思路即上圖,上圖中箭頭顏色由淺到深即為呼叫順序,大概講解一下
1,首先要把網路請求封裝成物件,即圖中TestApi(繼承於LXBaseRequest),在Viewcontroller中呼叫介面loadData
2,這時會呼叫到TestApi的父類LXBaseRequest中的loadData方法, 並從TestApi實現的重寫或者協議方法中獲取url, 請求型別, 引數等資訊, 呼叫LXRequestProxy中的請求方法
3, LXRequestProxy內部呼叫AF的GET或其他方法
4, 回撥之後並沒有直接返回responseObject,而是轉換成LXResponse,這樣返回的資料經過封裝, 相當於從後面也進行了隔離, 比如AF2.x的時候回撥block的引數還是^(AFHTTPRequestOperation *operation, id reponseObject)
,AF3.x就變成了^(NSURLSessionDataTask *task, id reponseObject)
,如果不轉換一下, 直接返回到控制器,改起來就尷尬了。。。
5,一路回撥到TestApi, 再到ViewController
6,走完之後再看一下快取如何處理,首先,快取一定分為存和取,
存,在第5步, 一路回撥到父類中的successCallApi這一步,將回撥資料存起來的(用GET+登入狀態或其他+url+引數轉換的字串作為key,這個隨意,適合專案即可)。
取,在第2步呼叫父類LXBaseRequest中的loadData時會先檢測該介面對應的資料是否存在, 存在直接返回父類LXBaseRequest中的successCallApi,不存在則正常發出請求
三, 舉個栗子
介面如何定義?
FirstTestApi.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#import "LXBaseRequest.h" //要遵守什麼協議取決於這個介面需要如何構造, LXBaseRequestDelegate一定要遵守,用於獲取url等基本資訊 @interface FirstTestApi : LXBaseRequest //呼叫介面需要的引數,可以從控制器賦值傳過來 @property (nonatomic, assign) int tp; /** 是否為讀取新資料(對應下拉重新整理) */ @property (nonatomic, assign) BOOL isLoadNew; /** 是否為最後一頁 */ @property (nonatomic, assign) BOOL isLastPage; /** 可以是轉好的資料模型,這裡只是示意一下 */ @property (nonatomic, strong) id dataModel; @property (nonatomic, assign) NSUInteger dataLength; @end |
FirstTestApi.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
#import "FirstTestApi.h" //基本url,可定義成全域性變數或者巨集, 拼接URL使用 #define BaseUrl @"http://api.zsreader.com/v2/" @implementation FirstTestApi { int page;//記錄頁碼值 int pageSize;//每頁條數 NSInteger total;//記錄總條數 } //重寫init方法是為了設定獲取引數和請求頭的代理, LXBaseRequestDelegate不用設定是因為已在父類中設定好, 如果只需要最基本的LXBaseRequestDelegate則不需要重寫init - (instancetype)init { self = [super init]; if (self) { self.paramSource = self; self.headerSource = self; } return self; } - (LXBaseRequestType)requestType { return LXBaseRequestTypeGet; } //1. 如果是POST請求下面兩個方法都不用寫 //2. 如果介面需要快取, 這個可以不寫,預設需要 //3. 某些GET介面, 不需要快取一定要寫, 比如需要資料及時變化的 - (BOOL)shouldCache { return YES; } //1. 刪除快取的時候要用來拼接快取的key, 所以如果使用了快取, 這個方法最好要寫, 簡單點就是直接返回requestUrl, 複雜一點的情況, 寫各種不同的url的共同部分, 比如一個介面類裡面有兩個相近的url,@"pub/home/2"和@"pub/found/2", 則可以寫@"pub/", 區共同部分,清快取時都清掉 //2. 不能引用當前類的變數, 直接崩掉, self.的東西都不行 //3. 如果有不同情況, (@"情況1"|@"情況2"), 比如 @"pub/home" 和 @"pub/found" 可以寫[NSString stringWithFormat:@"%@(%@|%@)", BaseUrl, @"pub/home", @"pub/found"]; - (NSString *)cacheRegexKey { return [NSString stringWithFormat:@"%@%@", BaseUrl, @"pub/home/2"]; } - (NSString *)requestUrl { return [NSString stringWithFormat:@"%@%@", BaseUrl, @"pub/home/2"]; } //虛擬碼, 本介面不需要header, 演示一下 - (NSDictionary *)headersForRequest:(LXBaseRequest *)request { // if (app.isLogin) { // return @{@"token":app.token}; // } return nil; } - (NSDictionary *)paramsForRequest:(LXBaseRequest *)request { //假如是上拉重新整理,取下一頁 NSMutableDictionary *params = [NSMutableDictionary dictionary]; if (!self.isLoadNew) { params[@"page"]=@(page); } params[@"tp"] = @(self.tp); return [params copy]; } //在呼叫API之前額外新增一些引數,但不應該在這個函式裡面修改已有的引數 //如果實現這個方法, 一定要在傳入的params基礎上修改 , 再返回修改後的params - (NSDictionary *)reformParams:(NSDictionary *)params { NSMutableDictionary *mParams = [params mutableCopy]; [mParams setObject:@"test" forKey:@"test"]; return [mParams copy]; } //獲取到包裝之後的LXResponse型別的返回資料,先處理一下,比如講資料轉成模型,供控制器回撥使用 -(void)beforePerformSuccessWithResponse:(LXResponse *)response { [super beforePerformSuccessWithResponse:response]; if(self.isLoadNew){ page=1; pageSize = [response.content[@"count"] intValue]; total = [response.content[@"total"] integerValue]; } self.isLastPage=(total<=page*pageSize); //可以在這轉好模型, 控制器裡直接用 self.dataModel = response.result; self.dataLength = response.responseData.length; page++; } @end |
控制器中如何使用?
ViewController.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#import "ViewController.h" #import "FirstTestApi.h" //遵守回撥協議LXBaseRequestCallBackDelegate @interface ViewController () //定義介面屬性實現懶載入 @property (nonatomic, strong) FirstTestApi *firstApi; @end @implementation ViewController #pragma mark --------- 懶載入 -------- - (FirstTestApi *)firstApi { if (!_firstApi) { _firstApi = [[FirstTestApi alloc] init]; _firstApi.delegate = self; _firstApi.tp = 1; } return _firstApi; } - (void)viewDidLoad { [super viewDidLoad]; //新增點選呼叫介面的按鈕 [self configBtns]; } //firstApi呼叫重新整理, 即page = 1 - (void)loadNew { self.firstApi.isLoadNew=YES; [self.firstApi loadData]; } //firstApi呼叫載入更多, 即page++ - (void)loadMore { self.firstApi.isLoadNew=NO; [self.firstApi loadData]; } #pragma mark --------- LXBaseRequestCallBackDelegate -------- - (void)requestDidSuccess:(LXBaseRequest *)request { if ([request isKindOfClass:[FirstTestApi class]]) { FirstTestApi *testApi = (FirstTestApi *)request; NSLog(@"介面1請求成功 %lu ", testApi.dataLength); } } - (void)requestDidFailed:(LXBaseRequest *)request { NSLog(@"請求失敗"); } @end |
四,還有那些功能及注意
1,如何清除快取, 比如在A介面的某個操作導致B,C介面資料都應發生變化, 這時切換到B,C介面(快取有效時間內,預設30秒,可在LXNetworkConfiguration
中設定),如果還是使用快取就不對了。
方式1: 如果在需要清快取的時刻能獲取到BC介面相應介面, 可呼叫[B.firstApi deleteCache]
,介面父類LXBaseRequest的deleteCache方法即可清除快取
方式2: 一般情況下, 在A介面操作的時候可能已經獲取不到BC介面的介面了,這是可以通過通知清除,讓AppDelegate監聽清除快取的通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#import "AppDelegate.h" #import "LXNetworkConfiguration.h" #import "LXCache.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInvalidCache:) name:LXDeleteCacheNotification object:nil]; return YES; } -(void)handleInvalidCache:(NSNotification*) notify { NSDictionary* dict = notify.userInfo; NSArray* arr = [dict objectForKey:LXDeleteCacheKey]; for (Class cls in arr) { [[LXCache sharedInstance] deleteCacheWithClass:cls]; } } |
A介面某個操作成功之後
1 2 3 4 5 6 7 |
[[NSNotificationCenter defaultCenter] postNotificationName:LXDeleteCacheNotification object:nil userInfo:@{LXDeleteCacheKey : @[NSClassFromString(@"TestApi"), NSClassFromString(@"xxxApi"), NSClassFromString(@"xxxApi"), ]}]; |
2,如果一個介面內有多個api怎麼辦, 都在requestDidSuccess裡通過型別寫if else 很亂, 實現requestDicWithClassStrAndSELStr將回撥分發到單獨的方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#pragma mark --------- LXBaseRequestCallBackDelegate -------- //@required方法, 實現一下, 不用寫東西 - (void)requestDidSuccess:(LXBaseRequest *)request { } //如果一個控制器記憶體在多個介面,並使用協議代理方式回撥,則可以實現下面方法,將各個請求回撥分發到各自的方法裡,字典key為介面類名,value為方法的selector字串 - (NSDictionary *)requestDicWithClassStrAndSELStr { NSMutableDictionary *dic = [NSMutableDictionary dictionary]; [dic setObject:NSStringFromSelector(@selector(handleFirstTestApi:)) forKey:@"FirstTestApi"]; [dic setObject:NSStringFromSelector(@selector(handleSecondTestApi:)) forKey:@"SecondTestApi"]; return [dic copy]; } - (void)handleFirstTestApi:(FirstTestApi *)api { NSLog(@"介面1請求成功 %lu ", api.dataLength); } - (void)handleSecondTestApi:(SecondTestApi *)api { NSLog(@"介面2請求成功 %@ ", api.dataModel); } - (void)requestDidFailed:(LXBaseRequest *)request { NSLog(@"請求失敗"); } |
3,如何使用block回撥,注意使用這種方式回撥不用遵守LXBaseRequestCallBackDelegate
協議和設定self.secondApi.delegate = self
1 2 3 4 5 6 7 8 9 10 |
- (void)loadSecondApi { [self.secondApi loadDataWithSuccess:^(LXBaseRequest *request) { SecondTestApi *testApi = (SecondTestApi *)request; NSLog(@"block 介面2請求成功 %@ ", testApi.dataModel); } fail:^(LXBaseRequest *request) { NSLog(@"介面2請求失敗"); }]; } |
4,介面有動態加密的快取處理, 如果介面有加密, 並且融合了時間戳等,每次呼叫的簽名都不一致的話,由於快取的key拼接了引數, 如果把動態變化的簽名也拼進去就永遠找不到快取,那麼就需要修改以下程式碼, 將下面程式碼中註釋部分開啟, 在if條件中排除會動態變化的引數的key
NSDictionary+LXNetworkParams.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#import "NSDictionary+LXNetworkParams.h" #import "NSArray+LXNetworkParams.h" @implementation NSDictionary (LXNetworkParams) /** params 轉換為NSString */ - (NSString *)lxUrlParamsToString { //字典排序 NSArray *sortedArray = [self lxUrlParamsToArray]; //陣列生成字串 return [sortedArray lxUrlParamArrayToString]; } - (NSArray *)lxUrlParamsToArray { NSMutableArray *result = [[NSMutableArray alloc] init]; [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if (![obj isKindOfClass:[NSString class]]) { obj = [NSString stringWithFormat:@"%@", obj]; } obj = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)obj, NULL, (CFStringRef)@"!*'();:@&;=+$,/?%#[]", kCFStringEncodingUTF8)); // if ([obj length] > 0 && (![key isEqualToString:TIMESTAMP_KEY] && ![key isEqualToString:SIGNATURE_KEY]) ) { [result addObject:[NSString stringWithFormat:@"%@=%@", key, obj]]; // } }]; NSArray *sortedResult = [result sortedArrayUsingSelector:@selector(compare:)]; return sortedResult; } @end |
5,其他功能用法見原始碼,有問題或者建議歡迎交流溝通