本框架實(shí)現(xiàn)思路與YTKNetwork和RTNetworking類似,相當(dāng)于一個(gè)簡(jiǎn)單版,把每一個(gè)網(wǎng)絡(luò)請(qǐng)求封裝成對(duì)象。使用LXNetwork,你的每一個(gè)請(qǐng)求都需要繼承LXBaseRequest類,通過(guò)覆蓋父類的一些方法或者實(shí)現(xiàn)相關(guān)協(xié)議方法來(lái)構(gòu)造指定的網(wǎng)絡(luò)請(qǐng)求。這個(gè)網(wǎng)絡(luò)庫(kù)可直接在項(xiàng)目中使用,但是有些功能完成度不是很完美,待完善。
GitHud地址:https://github.com/CoderLXWang/LXNetwork
一、為什么要這樣做?
實(shí)現(xiàn)思路的圖在下面,可以對(duì)比著圖看下面內(nèi)容。
直接封裝一個(gè)簡(jiǎn)易的HttpTool,里面直接調(diào)用AF,返回responseObject直接返回, 這樣不行嗎, 為什么要弄這么麻煩?
顯而易見的優(yōu)點(diǎn)大概有以下幾點(diǎn):
1,前后隔離AFNetworking,以后如果升級(jí)AF或者替換其他框架, 只需要改動(dòng)直接與AF接觸的LXRequestProxy和LXResponse內(nèi)的代碼即可,避免對(duì)項(xiàng)目中業(yè)務(wù)代碼產(chǎn)生影響(半小時(shí)完成從AF2.6升級(jí)AF3.0,重度使用的三方框架一般都要隔離一下)
2,將每個(gè)接口抽象成一個(gè)類,易于管理,按每個(gè)接口的需求構(gòu)造請(qǐng)求(比如有的接口要緩存,有的接口不要緩存)
3,所有接口調(diào)用都經(jīng)過(guò)LXBaseRequest,可以方便的在基類中處理公共邏輯(比如項(xiàng)目全部完成了,突然要用請(qǐng)求參數(shù)排序,加鹽等方式加密)
缺點(diǎn):使用麻煩。。。。。

二、思路講解
包括緩存在內(nèi)的大體思路即上圖,上圖中箭頭顏色由淺到深即為調(diào)用順序,大概講解一下
1,首先要把網(wǎng)絡(luò)請(qǐng)求封裝成對(duì)象,即圖中TestApi(繼承于LXBaseRequest),在Viewcontroller中調(diào)用接口loadData
2,這時(shí)會(huì)調(diào)用到TestApi的父類LXBaseRequest中的loadData方法, 并從TestApi實(shí)現(xiàn)的重寫或者協(xié)議方法中獲取url, 請(qǐng)求類型, 參數(shù)等信息, 調(diào)用LXRequestProxy中的請(qǐng)求方法
3, LXRequestProxy內(nèi)部調(diào)用AF的GET或其他方法
4, 回調(diào)之后并沒(méi)有直接返回responseObject,而是轉(zhuǎn)換成LXResponse,這樣返回的數(shù)據(jù)經(jīng)過(guò)封裝, 相當(dāng)于從后面也進(jìn)行了隔離, 比如AF2.x的時(shí)候回調(diào)block的參數(shù)還是^(AFHTTPRequestOperation *operation, id reponseObject),AF3.x就變成了^(NSURLSessionDataTask *task, id reponseObject),如果不轉(zhuǎn)換一下, 直接返回到控制器,改起來(lái)就尷尬了。。。
5,一路回調(diào)到TestApi, 再到ViewController
6,走完之后再看一下緩存如何處理,首先,緩存一定分為存和取,
存,在第5步, 一路回調(diào)到父類中的successCallApi這一步,將回調(diào)數(shù)據(jù)存起來(lái)的(用GET+登錄狀態(tài)或其他+url+參數(shù)轉(zhuǎn)換的字符串作為key,這個(gè)隨意,適合項(xiàng)目即可)。
取,在第2步調(diào)用父類LXBaseRequest中的loadData時(shí)會(huì)先檢測(cè)該接口對(duì)應(yīng)的數(shù)據(jù)是否存在, 存在直接返回父類LXBaseRequest中的successCallApi,不存在則正常發(fā)出請(qǐng)求
三, 舉個(gè)栗子
接口如何定義?
FirstTestApi.h
#import "LXBaseRequest.h"
//要遵守什么協(xié)議取決于這個(gè)接口需要如何構(gòu)造, LXBaseRequestDelegate一定要遵守,用于獲取url等基本信息
@interface FirstTestApi : LXBaseRequest<LXBaseRequestDelegate, LXBaseRequestParamDelegate, LXBaseRequestHeaderDelegate>
//調(diào)用接口需要的參數(shù),可以從控制器賦值傳過(guò)來(lái)
@property (nonatomic, assign) int tp;
/** 是否為讀取新數(shù)據(jù)(對(duì)應(yīng)下拉刷新) */
@property (nonatomic, assign) BOOL isLoadNew;
/** 是否為最后一頁(yè) */
@property (nonatomic, assign) BOOL isLastPage;
/** 可以是轉(zhuǎn)好的數(shù)據(jù)模型,這里只是示意一下 */
@property (nonatomic, strong) id dataModel;
@property (nonatomic, assign) NSUInteger dataLength;
@end
FirstTestApi.m
#import "FirstTestApi.h"
//基本url,可定義成全局變量或者宏, 拼接URL使用
#define BaseUrl @"http://api.zsreader.com/v2/"
@implementation FirstTestApi
{
int page;//記錄頁(yè)碼值
int pageSize;//每頁(yè)條數(shù)
NSInteger total;//記錄總條數(shù)
}
//重寫init方法是為了設(shè)置獲取參數(shù)和請(qǐng)求頭的代理, LXBaseRequestDelegate不用設(shè)置是因?yàn)橐言诟割愔性O(shè)置好, 如果只需要最基本的LXBaseRequestDelegate則不需要重寫init
- (instancetype)init
{
self = [super init];
if (self) {
self.paramSource = self;
self.headerSource = self;
}
return self;
}
- (LXBaseRequestType)requestType {
return LXBaseRequestTypeGet;
}
//1. 如果是POST請(qǐng)求下面兩個(gè)方法都不用寫
//2. 如果接口需要緩存, 這個(gè)可以不寫,默認(rèn)需要
//3. 某些GET接口, 不需要緩存一定要寫, 比如需要數(shù)據(jù)及時(shí)變化的
- (BOOL)shouldCache {
return YES;
}
//1. 刪除緩存的時(shí)候要用來(lái)拼接緩存的key, 所以如果使用了緩存, 這個(gè)方法最好要寫, 簡(jiǎn)單點(diǎn)就是直接返回requestUrl, 復(fù)雜一點(diǎn)的情況, 寫各種不同的url的共同部分, 比如一個(gè)接口類里面有兩個(gè)相近的url,@"pub/home/2"和@"pub/found/2", 則可以寫@"pub/", 區(qū)共同部分,清緩存時(shí)都清掉
//2. 不能引用當(dāng)前類的變量, 直接崩掉, 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 {
//假如是上拉刷新,取下一頁(yè)
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (!self.isLoadNew) {
params[@"page"]=@(page);
}
params[@"tp"] = @(self.tp);
return [params copy];
}
//在調(diào)用API之前額外添加一些參數(shù),但不應(yīng)該在這個(gè)函數(shù)里面修改已有的參數(shù)
//如果實(shí)現(xiàn)這個(gè)方法, 一定要在傳入的params基礎(chǔ)上修改 , 再返回修改后的params
- (NSDictionary *)reformParams:(NSDictionary *)params {
NSMutableDictionary *mParams = [params mutableCopy];
[mParams setObject:@"test" forKey:@"test"];
return [mParams copy];
}
//獲取到包裝之后的LXResponse類型的返回?cái)?shù)據(jù),先處理一下,比如講數(shù)據(jù)轉(zhuǎn)成模型,供控制器回調(diào)使用
-(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);
//可以在這轉(zhuǎn)好模型, 控制器里直接用
self.dataModel = response.result;
self.dataLength = response.responseData.length;
page++;
}
@end
控制器中如何使用?
ViewController.h
#import "ViewController.h"
#import "FirstTestApi.h"
//遵守回調(diào)協(xié)議LXBaseRequestCallBackDelegate
@interface ViewController () <LXBaseRequestCallBackDelegate>
//定義接口屬性實(shí)現(xiàn)懶加載
@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];
//添加點(diǎn)擊調(diào)用接口的按鈕
[self configBtns];
}
//firstApi調(diào)用刷新, 即page = 1
- (void)loadNew {
self.firstApi.isLoadNew=YES;
[self.firstApi loadData];
}
//firstApi調(diào)用加載更多, 即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請(qǐng)求成功 %lu ", testApi.dataLength);
}
}
- (void)requestDidFailed:(LXBaseRequest *)request {
NSLog(@"請(qǐng)求失敗");
}
@end
四,還有那些功能及注意
1,如何清除緩存, 比如在A界面的某個(gè)操作導(dǎo)致B,C界面數(shù)據(jù)都應(yīng)發(fā)生變化, 這時(shí)切換到B,C界面(緩存有效時(shí)間內(nèi),默認(rèn)30秒,可在LXNetworkConfiguration中設(shè)置),如果還是使用緩存就不對(duì)了。
方式1: 如果在需要清緩存的時(shí)刻能獲取到BC界面相應(yīng)接口, 可調(diào)用[B.firstApi deleteCache],接口父類LXBaseRequest的deleteCache方法即可清除緩存
方式2: 一般情況下, 在A界面操作的時(shí)候可能已經(jīng)獲取不到BC界面的接口了,這是可以通過(guò)通知清除,讓AppDelegate監(jiān)聽清除緩存的通知
#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界面某個(gè)操作成功之后
[[NSNotificationCenter defaultCenter] postNotificationName:LXDeleteCacheNotification
object:nil
userInfo:@{LXDeleteCacheKey
: @[NSClassFromString(@"TestApi"),
NSClassFromString(@"xxxApi"),
NSClassFromString(@"xxxApi"),
]}];
2,如果一個(gè)界面內(nèi)有多個(gè)api怎么辦, 都在requestDidSuccess里通過(guò)類型寫if else 很亂, 實(shí)現(xiàn)requestDicWithClassStrAndSELStr將回調(diào)分發(fā)到單獨(dú)的方法中
#pragma mark --------- LXBaseRequestCallBackDelegate --------
//@required方法, 實(shí)現(xiàn)一下, 不用寫東西
- (void)requestDidSuccess:(LXBaseRequest *)request {
}
//如果一個(gè)控制器內(nèi)存在多個(gè)接口,并使用協(xié)議代理方式回調(diào),則可以實(shí)現(xiàn)下面方法,將各個(gè)請(qǐng)求回調(diào)分發(fā)到各自的方法里,字典key為接口類名,value為方法的selector字符串
- (NSDictionary<NSString *, NSString *> *)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請(qǐng)求成功 %lu ", api.dataLength);
}
- (void)handleSecondTestApi:(SecondTestApi *)api {
NSLog(@"接口2請(qǐng)求成功 %@ ", api.dataModel);
}
- (void)requestDidFailed:(LXBaseRequest *)request {
NSLog(@"請(qǐng)求失敗");
}
3,如何使用block回調(diào),注意使用這種方式回調(diào)不用遵守LXBaseRequestCallBackDelegate協(xié)議和設(shè)置self.secondApi.delegate = self
- (void)loadSecondApi {
[self.secondApi loadDataWithSuccess:^(LXBaseRequest *request) {
SecondTestApi *testApi = (SecondTestApi *)request;
NSLog(@"block 接口2請(qǐng)求成功 %@ ", testApi.dataModel);
} fail:^(LXBaseRequest *request) {
NSLog(@"接口2請(qǐng)求失敗");
}];
}
4,接口有動(dòng)態(tài)加密的緩存處理, 如果接口有加密, 并且融合了時(shí)間戳等,每次調(diào)用的簽名都不一致的話,由于緩存的key拼接了參數(shù), 如果把動(dòng)態(tài)變化的簽名也拼進(jìn)去就永遠(yuǎn)找不到緩存,那么就需要修改以下代碼, 將下面代碼中注釋部分打開, 在if條件中排除會(huì)動(dòng)態(tài)變化的參數(shù)的key
NSDictionary+LXNetworkParams.m
#import "NSDictionary+LXNetworkParams.h"
#import "NSArray+LXNetworkParams.h"
@implementation NSDictionary (LXNetworkParams)
/** params 轉(zhuǎn)換為NSString */
- (NSString *)lxUrlParamsToString {
//字典排序
NSArray *sortedArray = [self lxUrlParamsToArray];
//數(shù)組生成字符串
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,其他功能用法見源碼,有問(wèn)題或者建議歡迎交流溝通