LXNetwork - 基于AF3.0封裝的iOS網(wǎng)絡(luò)請(qǐng)求庫(kù)

本框架實(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):使用麻煩。。。。。

實(shí)現(xiàn)思路.png

二、思路講解
包括緩存在內(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)題或者建議歡迎交流溝通


GitHud地址:https://github.com/CoderLXWang/LXNetwork

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS網(wǎng)絡(luò)架構(gòu)討論梳理整理中。。。 其實(shí)如果沒(méi)有APIManager這一層是沒(méi)法使用delegate的,畢竟多個(gè)單...
    yhtang閱讀 5,494評(píng)論 1 23
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評(píng)論 25 708
  • AFHTTPRequestOperationManager 網(wǎng)絡(luò)傳輸協(xié)議UDP、TCP、Http、Socket、X...
    Carden閱讀 5,328評(píng)論 0 12
  • 致自己,愿不負(fù)時(shí)光也終不負(fù)自己
    李睿_閱讀 188評(píng)論 0 0
  • 懂你,是一種簡(jiǎn)簡(jiǎn)單單的感動(dòng),懂得你的眼淚,給你最暖的安慰;深知你的憔悴,怪你為何不知疲憊。 懂你,是一種難言的柔情...
    葉樣悠閱讀 364評(píng)論 4 10

友情鏈接更多精彩內(nèi)容