基于AFNetworking3.1的二次封裝和拓展

前言

之前做項(xiàng)目的時(shí)候,由于需要重構(gòu)網(wǎng)絡(luò)層,所以自己參考網(wǎng)上的方法對(duì)AFNeworking做了一次二次封裝,我把一些業(yè)務(wù)耦合的東西去掉了之后,整理并增加了緩存策略等一些功能就分享出來(lái)。

概述

AFN3.1其實(shí)已經(jīng)很封裝的很好了,但是還是有一些需求需要自己添加。比如說(shuō)緩存策略、重復(fù)請(qǐng)求管理功能,這些AFN3.1都沒(méi)有提供直接的方法,所以我在AFN3.1之上做了一層封裝,API面向業(yè)務(wù)層更加友好。

YQNetworking是一個(gè)集約型框架,發(fā)起請(qǐng)求集中在一個(gè)類(lèi)上,統(tǒng)一管理,適合中小型的項(xiàng)目,需要對(duì)網(wǎng)路請(qǐng)求進(jìn)行更加細(xì)致的配置和管理,這個(gè)網(wǎng)絡(luò)框架就可能不太適合。框架目錄如下

目錄結(jié)構(gòu).png

方案設(shè)計(jì)

主要為大家講解重復(fù)管理功能設(shè)計(jì)和緩存方案設(shè)計(jì)

重復(fù)請(qǐng)求管理方案設(shè)計(jì)

GET和POST的API有refresh參數(shù),這個(gè)參數(shù)的主要目的是用于刷新請(qǐng)求,當(dāng)遇到重復(fù)請(qǐng)求時(shí),若為YES,則會(huì)取消舊的請(qǐng)求,用新的方法,若為NO,則忽略新請(qǐng)求,用舊請(qǐng)求,大家針對(duì)自己的業(yè)務(wù)需求自己取舍。判斷代碼如下:

if ([self haveSameRequestInTasksPool:session] && !refresh) {
        //取消新請(qǐng)求
        [session cancel];
        return session;
    }else {
        //無(wú)論是否有舊請(qǐng)求,先執(zhí)行取消舊請(qǐng)求,反正都需要刷新請(qǐng)求
        YQURLSessionTask *oldTask = [self cancleSameRequestInTasksPool:session];
        if (oldTask) [[self allTasks] removeObject:oldTask];
        if (session) [[self allTasks] addObject:session];
        [session resume];
        return session;
    }

判斷的相關(guān)邏輯在RequestManager.h這個(gè)分類(lèi)文件中,大家可以看下它提供的API,注釋在代碼中:

@interface YQNetworking (RequestManager)
/**
 *  判斷網(wǎng)絡(luò)請(qǐng)求池中是否有相同的請(qǐng)求
 *
 *  @param task 網(wǎng)絡(luò)請(qǐng)求任務(wù)
 *
 *  @return bool
 */
+ (BOOL)haveSameRequestInTasksPool:(YQURLSessionTask *)task;

/**
 *  如果有舊請(qǐng)求則取消舊請(qǐng)求
 *
 *  @param task 新請(qǐng)求
 *
 *  @return 舊請(qǐng)求
 */
+ (YQURLSessionTask *)cancleSameRequestInTasksPool:(YQURLSessionTask *)task;

判斷一個(gè)請(qǐng)求是否重復(fù),也就是判斷新來(lái)的請(qǐng)求和舊的請(qǐng)求是否一樣,判斷的依據(jù)有一些愛(ài)幾點(diǎn):

  1. 請(qǐng)求的方法是否相同,是否同為GET和POST;
  2. 請(qǐng)求的URL是否相同,如果是GET請(qǐng)求,到這一步就可以做出判斷了,如果是POST請(qǐng)求,則還需進(jìn)行下一步的驗(yàn)證;
  3. 請(qǐng)求體的內(nèi)容是否相同(POST請(qǐng)求的參數(shù)放在HTTP body里);

遍歷YQNetworking當(dāng)前的運(yùn)行任務(wù)(調(diào)動(dòng)currentRunningTasks獲取當(dāng)前的運(yùn)行任務(wù)),根據(jù)任務(wù)源請(qǐng)求判斷新來(lái)的請(qǐng)求,是否已經(jīng)有相同的請(qǐng)求正在執(zhí)行當(dāng)中:

    + (BOOL)haveSameRequestInTasksPool:(XDURLSessionTask *)task {
    __block BOOL isSame = NO;
    [[self currentRunningTasks] enumerateObjectsUsingBlock:^(XDURLSessionTask *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([task.originalRequest isTheSameRequest:obj.originalRequest]) {
            isSame  = YES;
            *stop = YES;
        }
    }];
    return isSame;
      }

取消舊的請(qǐng)求邏輯也很容易實(shí)現(xiàn),遍歷獲取重復(fù)的請(qǐng)求,調(diào)用它的cancel方法就可以了

緩存方案的設(shè)計(jì)

先說(shuō)說(shuō)如何啟動(dòng)我們的緩存機(jī)制,GET和POST的API有一個(gè)cache參數(shù),它用于給大家決定是否開(kāi)啟緩存機(jī)制,大家針對(duì)自己的業(yè)務(wù)數(shù)據(jù)的特征來(lái)決定是否開(kāi)啟cache,即時(shí)性和時(shí)效性的數(shù)據(jù)建議不開(kāi)啟緩存,一般建議開(kāi)啟,開(kāi)啟緩存后會(huì)回調(diào)兩次,第一次獲取是緩存數(shù)據(jù),第二次獲取的是最新的網(wǎng)絡(luò)數(shù)據(jù)

在上邊我們已經(jīng)分析了NSURLCache的局限性,基于HTTP緩存機(jī)制的緩存方案需要客戶(hù)端和服務(wù)器雙邊配合。所以我自己設(shè)計(jì)了一個(gè)網(wǎng)絡(luò)的請(qǐng)求方案,思路來(lái)自SDWebImage
我的緩存方案分兩級(jí)緩存:內(nèi)存緩存和磁盤(pán)緩存,緩存的過(guò)程如下:

第一次請(qǐng)求獲取相應(yīng)數(shù)據(jù),先緩存到內(nèi)存,再緩存到磁盤(pán),下一次再發(fā)起相同的請(qǐng)求時(shí),會(huì)先查找內(nèi)存之中會(huì)不會(huì)有相應(yīng)的緩存,如果有則返回緩存數(shù)據(jù),如果沒(méi)有,則向磁盤(pán)查找,如果磁盤(pán)存在緩存則返回,否則發(fā)起網(wǎng)絡(luò)請(qǐng)求獲取數(shù)據(jù)

上面就是緩存的整一個(gè)過(guò)程,思路還是比較清晰,除此之外,緩存的設(shè)計(jì)還需要考慮兩個(gè)問(wèn)題:

  1. 緩存的淘汰策略
  2. 緩存的過(guò)期機(jī)制

YQCacheManager是一個(gè)緩存管理類(lèi),暴露出簡(jiǎn)單的API給XDNetworking進(jìn)行緩存的存取,底層是使用YQMemoryCache(NSCache)進(jìn)行內(nèi)存緩存,使用YQDiskCache(NSFileManager)進(jìn)行磁盤(pán)緩存,緩存淘汰策略采用LRU算法(YQLRUManager)。它是一個(gè)單例,通過(guò)一個(gè)全局入口統(tǒng)一訪問(wèn):

+ (YQCacheManager *)shareManager;

默認(rèn)是磁盤(pán)大小是40MB,有效期是7天,如果想自定義設(shè)置,可以通過(guò)以下方法設(shè)置:

- (void)setCacheTime:(NSTimeInterval) time diskCapacity:(NSUInteger) capacity;

YQLRUManager

YQLRUManager是一個(gè)基于LRU(最近最少使用算法)實(shí)現(xiàn)的緩存數(shù)據(jù)管理類(lèi),它底層是由一個(gè)動(dòng)態(tài)數(shù)組實(shí)現(xiàn)的隊(duì)列,數(shù)組的元素的字典,字典包含兩個(gè)鍵:fileName (緩存文件名字)和date (緩存文件最近的訪問(wèn)時(shí)間),這個(gè)隊(duì)列保存在NSUserDefault里。

LRU算法的實(shí)現(xiàn):
創(chuàng)建一個(gè)隊(duì)列,新加的節(jié)點(diǎn)添加在隊(duì)列的尾部;命中緩存時(shí),調(diào)整結(jié)點(diǎn)的位置,將其放在隊(duì)列的尾部;要淘汰緩存時(shí),刪除隊(duì)列的頭部節(jié)點(diǎn)。

應(yīng)用場(chǎng)景:

  1. 當(dāng)有數(shù)據(jù)緩存時(shí),會(huì)調(diào)用YQLRUManager的addFileNode方法在LRU隊(duì)列上記錄一個(gè)文件節(jié)點(diǎn),文件結(jié)點(diǎn)也就是上面解釋的字典,記錄文件名和此時(shí)的訪問(wèn)時(shí)間,先判斷隊(duì)列是否已經(jīng)存在同文件名的結(jié)點(diǎn),如果有則將節(jié)點(diǎn)去除并插入到隊(duì)列的尾部,沒(méi)有則直接插入到尾部。在遍歷隊(duì)列查找同文件名的結(jié)點(diǎn)的時(shí)候我做了遍歷優(yōu)化,先將隊(duì)列逆序,在查找,因?yàn)樵谖膊康慕Y(jié)點(diǎn)被重用的概率會(huì)打一些,從尾部查找會(huì)減少遍歷的次數(shù):

    //優(yōu)化遍歷
    NSArray *reverseArray = [[array reverseObjectEnumerator] allObjects];
    
    [reverseArray enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj[@"fileName"] isEqualToString:filename]) {
            [operationQueue removeObjectAtIndex:idx];
            *stop = YES;
        }
    
    }];
    
  1. 當(dāng)時(shí)用緩存的時(shí)候,說(shuō)明緩存文件被訪問(wèn),所有應(yīng)修改LRU隊(duì)列對(duì)應(yīng)文件結(jié)點(diǎn)的最近訪問(wèn)并它插入LRU的尾部,我們通過(guò)調(diào)用refreshIndexOfFileNode方法實(shí)現(xiàn),它的實(shí)現(xiàn)原理根addFileNode一樣。

  2. 當(dāng)刪除LRU緩存的時(shí)候,調(diào)用removeLRUFileNodeWithCacheTime方法,他需要傳一個(gè)有效期的參數(shù),這個(gè)參數(shù)由上層的YQCacheManager提供。遍歷LRU隊(duì)列,從頭部開(kāi)始刪除,刪掉已經(jīng)過(guò)期的文件節(jié)點(diǎn),用一個(gè)數(shù)組保存刪除的文件節(jié)點(diǎn)里的文件名,用于回調(diào)給上層通過(guò)文件名刪除真正的磁盤(pán)緩存。如果沒(méi)有文件過(guò)期,則刪除頭結(jié)點(diǎn),它對(duì)應(yīng)著最近最少使用的文件:

    NSArray *tmpArray = [operationQueue copy];
    
        [tmpArray enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSDate *date = obj[@"date"];
            NSDate *newDate = [date dateByAddingTimeInterval:time];
            if ([[NSDate date] compare:newDate] == NSOrderedDescending) {
                [result addObject:obj[@"fileName"]];
                [operationQueue removeObjectAtIndex:idx];
            }
        }];
    

這里有個(gè)注意點(diǎn),就是你每次操作完LRU隊(duì)列,無(wú)論是增刪改查,都要強(qiáng)制刷新LRU隊(duì)列在NSUserDefault的緩存。

API設(shè)計(jì)

API面向業(yè)務(wù)更加友好,回調(diào)方式采用block,功能包括GET、POST、下載、單文件上傳、多文件上傳、請(qǐng)求管理、緩存管理

GET請(qǐng)求

/**
 *  GET請(qǐng)求
 *
 *  @param url              請(qǐng)求路徑
 *  @param cache            是否緩存
 *  @param refresh          是否刷新請(qǐng)求(遇到重復(fù)請(qǐng)求,若為YES,則會(huì)取消舊的請(qǐng)求,用新的請(qǐng)求,若為NO,則忽略新請(qǐng)   求,用舊請(qǐng)求)
 *  @param params           拼接參數(shù)
 *  @param progressBlock    進(jìn)度回調(diào)
 *  @param successBlock     成功回調(diào)
 *  @param failBlock        失敗回調(diào)
 *
 *  @return 返回的對(duì)象中可取消請(qǐng)求
 */
+ (YQURLSessionTask *)getWithUrl:(NSString *)url
                  refreshRequest:(BOOL)refresh
                           cache:(BOOL)cache
                          params:(NSDictionary *)params
                   progressBlock:(YQGetProgress)progressBlock
                    successBlock:(YQResponseSuccessBlock)successBlock
                       failBlock:(YQResponseFailBlock)failBlock;

POST請(qǐng)求

/**
 *  POST請(qǐng)求
 *
 *  @param url              請(qǐng)求路徑
 *  @param cache            是否緩存
 *  @param refresh          解釋同上
 *  @param params           拼接參數(shù)
 *  @param progressBlock    進(jìn)度回調(diào)
 *  @param successBlock     成功回調(diào)
 *  @param failBlock        失敗回調(diào)
 *
 *  @return 返回的對(duì)象中可取消請(qǐng)求
 */
+ (YQURLSessionTask *)postWithUrl:(NSString *)url
                   refreshRequest:(BOOL)refresh
                            cache:(BOOL)cache
                           params:(NSDictionary *)params
                    progressBlock:(YQPostProgress)progressBlock
                     successBlock:(YQResponseSuccessBlock)successBlock
                        failBlock:(YQResponseFailBlock)failBlock;

下載請(qǐng)求

/**
 *  文件下載
 *
 *  @param url           下載文件接口地址
 *  @param progressBlock 下載進(jìn)度
 *  @param successBlock  成功回調(diào)
 *  @param failBlock     下載回調(diào)
 *
 *  @return 返回的對(duì)象可取消請(qǐng)求
 */
+ (YQURLSessionTask *)downloadWithUrl:(NSString *)url
                        progressBlock:(YQDownloadProgress)progressBlock
                         successBlock:(YQDownloadSuccessBlock)successBlock
                            failBlock:(YQDownloadFailBlock)failBlock;

文件上傳

/**
 *  文件上傳
 *
 *  @param url              上傳文件接口地址
 *  @param data             上傳文件數(shù)據(jù)
 *  @param type             上傳文件類(lèi)型
 *  @param name             上傳文件服務(wù)器文件夾名
 *  @param mimeType         mimeType
 *  @param progressBlock    上傳文件路徑
 *  @param successBlock     成功回調(diào)
 *  @param failBlock        失敗回調(diào)
 *
 *  @return 返回的對(duì)象中可取消請(qǐng)求
 */
+ (YQURLSessionTask *)uploadFileWithUrl:(NSString *)url
                                fileData:(NSData *)data
                                    type:(NSString *)type
                                    name:(NSString *)name
                                mimeType:(NSString *)mimeType
                           progressBlock:(YQUploadProgressBlock)progressBlock
                            successBlock:(YQResponseSuccessBlock)successBlock
                               failBlock:(YQResponseFailBlock)failBlock;

多文件上傳

/**
 *  多文件上傳
 *
 *  @param url           上傳文件地址
 *  @param datas         數(shù)據(jù)集合
 *  @param type          類(lèi)型
 *  @param name          服務(wù)器文件夾名
 *  @param mimeType      mimeTypes
 *  @param progressBlock 上傳進(jìn)度
 *  @param successBlock  成功回調(diào)
 *  @param failBlock     失敗回調(diào)
 *
 *  @return 任務(wù)集合
 */
+ (NSArray *)uploadMultFileWithUrl:(NSString *)url
                         fileDatas:(NSArray *)datas
                              type:(NSString *)type
                              name:(NSString *)name
                          mimeType:(NSString *)mimeTypes
                     progressBlock:(YQUploadProgressBlock)progressBlock
                      successBlock:(YQMultUploadSuccessBlock)successBlock
                         failBlock:(YQMultUploadFailBlock)failBlock;

獲取當(dāng)前正在運(yùn)行的任務(wù)

/**
 *  正在運(yùn)行的網(wǎng)絡(luò)任務(wù)
 *
 *  @return 
 */
+ (NSArray *)currentRunningTasks;

取消所有網(wǎng)絡(luò)請(qǐng)求

+ (void)cancleAllRequest;

取消下載請(qǐng)求

+ (void)cancelRequestWithURL:(NSString *)url;

獲取緩存大小

/**
 *  獲取緩存大小
 *
 *  @return 緩存大小
 */
+ (NSUInteger)totalCacheSize;

清理緩存

/**
 *  清除所有緩存
 */
+ (void)clearTotalCache;

自動(dòng)清理緩存

    //每次網(wǎng)絡(luò)請(qǐng)求的時(shí)候,檢查此時(shí)磁盤(pán)中的緩存大小,閾值默認(rèn)是40MB,如果超過(guò)閾值,則清理LRU緩
    //存,同時(shí)也會(huì)清理過(guò)期緩存,緩存默認(rèn)SSL是7天,磁盤(pán)緩存的大小和SSL的設(shè)置可以通過(guò)該方法
    //[YQCacheManager shareManager] setCacheTime: diskCapacity:]設(shè)置。
    
    [[YQCacheManager shareManager] clearLRUCache];

源碼地址

https://github.com/huangyingqiu/YQNetworking,喜歡的給個(gè)star!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 前言 最近一直在思考一個(gè)問(wèn)題,網(wǎng)絡(luò)層的要如何設(shè)計(jì)? 純粹的網(wǎng)絡(luò)層 何為純粹呢?就是服務(wù)器返回什么數(shù)據(jù),回調(diào)給業(yè)務(wù)層...
    欣東閱讀 1,433評(píng)論 4 31
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,820評(píng)論 25 708
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,837評(píng)論 18 139
  • 偷懶不是一種愛(ài)好,它只是人對(duì)事物一種本能的逃避,尤其是在比較困難的事物面前。人往往都會(huì)有這樣的一種情緒,在...
    苦中甘閱讀 343評(píng)論 1 3
  • [咖啡]最近很火的一首小詩(shī): 每個(gè)人都有自己的時(shí)間軸 紐約時(shí)間比加州時(shí)間早三個(gè)小時(shí), New York is 3 ...
    swiftlee閱讀 11,574評(píng)論 0 2