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

前言

之前做项目的时候,由于需要重构网络层,所以自己参考网上的方法对AFNeworking做了一次二次封装,我把一些业务耦合的东西去掉了之后,整理并增加了缓存策略等一些功能就分享出来。

概述

AFN3.1其实已经很封装的很好了,但是还是有一些需求需要自己添加。比如说缓存策略、重复请求管理功能,这些AFN3.1都没有提供直接的方法,所以我在AFN3.1之上做了一层封装,API面向业务层更加友好。

YQNetworking是一个集约型框架,发起请求集中在一个类上,统一管理,适合中小型的项目,需要对网路请求进行更加细致的配置和管理,这个网络框架就可能不太适合。框架目录如下

目录结构.png

方案设计

主要为大家讲解重复管理功能设计和缓存方案设计

重复请求管理方案设计

GET和POST的API有refresh参数,这个参数的主要目的是用于刷新请求,当遇到重复请求时,若为YES,则会取消旧的请求,用新的方法,若为NO,则忽略新请求,用旧请求,大家针对自己的业务需求自己取舍。判断代码如下:

if ([self haveSameRequestInTasksPool:session] && !refresh) {
        //取消新请求
        [session cancel];
        return session;
    }else {
        //无论是否有旧请求,先执行取消旧请求,反正都需要刷新请求
        YQURLSessionTask *oldTask = [self cancleSameRequestInTasksPool:session];
        if (oldTask) [[self allTasks] removeObject:oldTask];
        if (session) [[self allTasks] addObject:session];
        [session resume];
        return session;
    }

判断的相关逻辑在RequestManager.h这个分类文件中,大家可以看下它提供的API,注释在代码中:

@interface YQNetworking (RequestManager)
/**
 *  判断网络请求池中是否有相同的请求
 *
 *  @param task 网络请求任务
 *
 *  @return bool
 */
+ (BOOL)haveSameRequestInTasksPool:(YQURLSessionTask *)task;

/**
 *  如果有旧请求则取消旧请求
 *
 *  @param task 新请求
 *
 *  @return 旧请求
 */
+ (YQURLSessionTask *)cancleSameRequestInTasksPool:(YQURLSessionTask *)task;

判断一个请求是否重复,也就是判断新来的请求和旧的请求是否一样,判断的依据有一些爱几点:

  1. 请求的方法是否相同,是否同为GET和POST;
  2. 请求的URL是否相同,如果是GET请求,到这一步就可以做出判断了,如果是POST请求,则还需进行下一步的验证;
  3. 请求体的内容是否相同(POST请求的参数放在HTTP body里);

遍历YQNetworking当前的运行任务(调动currentRunningTasks获取当前的运行任务),根据任务源请求判断新来的请求,是否已经有相同的请求正在执行当中:

    + (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;
      }

取消旧的请求逻辑也很容易实现,遍历获取重复的请求,调用它的cancel方法就可以了

缓存方案的设计

先说说如何启动我们的缓存机制,GET和POST的API有一个cache参数,它用于给大家决定是否开启缓存机制,大家针对自己的业务数据的特征来决定是否开启cache,即时性和时效性的数据建议不开启缓存,一般建议开启,开启缓存后会回调两次,第一次获取是缓存数据,第二次获取的是最新的网络数据

在上边我们已经分析了NSURLCache的局限性,基于HTTP缓存机制的缓存方案需要客户端和服务器双边配合。所以我自己设计了一个网络的请求方案,思路来自SDWebImage
我的缓存方案分两级缓存:内存缓存和磁盘缓存,缓存的过程如下:

第一次请求获取相应数据,先缓存到内存,再缓存到磁盘,下一次再发起相同的请求时,会先查找内存之中会不会有相应的缓存,如果有则返回缓存数据,如果没有,则向磁盘查找,如果磁盘存在缓存则返回,否则发起网络请求获取数据

上面就是缓存的整一个过程,思路还是比较清晰,除此之外,缓存的设计还需要考虑两个问题:

  1. 缓存的淘汰策略
  2. 缓存的过期机制

YQCacheManager是一个缓存管理类,暴露出简单的API给XDNetworking进行缓存的存取,底层是使用YQMemoryCache(NSCache)进行内存缓存,使用YQDiskCache(NSFileManager)进行磁盘缓存,缓存淘汰策略采用LRU算法(YQLRUManager)。它是一个单例,通过一个全局入口统一访问:

+ (YQCacheManager *)shareManager;

默认是磁盘大小是40MB,有效期是7天,如果想自定义设置,可以通过以下方法设置:

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

YQLRUManager

YQLRUManager是一个基于LRU(最近最少使用算法)实现的缓存数据管理类,它底层是由一个动态数组实现的队列,数组的元素的字典,字典包含两个键:fileName (缓存文件名字)和date (缓存文件最近的访问时间),这个队列保存在NSUserDefault里。

LRU算法的实现:
创建一个队列,新加的节点添加在队列的尾部;命中缓存时,调整结点的位置,将其放在队列的尾部;要淘汰缓存时,删除队列的头部节点。

应用场景:

  1. 当有数据缓存时,会调用YQLRUManager的addFileNode方法在LRU队列上记录一个文件节点,文件结点也就是上面解释的字典,记录文件名和此时的访问时间,先判断队列是否已经存在同文件名的结点,如果有则将节点去除并插入到队列的尾部,没有则直接插入到尾部。在遍历队列查找同文件名的结点的时候我做了遍历优化,先将队列逆序,在查找,因为在尾部的结点被重用的概率会打一些,从尾部查找会减少遍历的次数:

    //优化遍历
    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. 当时用缓存的时候,说明缓存文件被访问,所有应修改LRU队列对应文件结点的最近访问并它插入LRU的尾部,我们通过调用refreshIndexOfFileNode方法实现,它的实现原理根addFileNode一样。

  2. 当删除LRU缓存的时候,调用removeLRUFileNodeWithCacheTime方法,他需要传一个有效期的参数,这个参数由上层的YQCacheManager提供。遍历LRU队列,从头部开始删除,删掉已经过期的文件节点,用一个数组保存删除的文件节点里的文件名,用于回调给上层通过文件名删除真正的磁盘缓存。如果没有文件过期,则删除头结点,它对应着最近最少使用的文件:

    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];
            }
        }];
    

这里有个注意点,就是你每次操作完LRU队列,无论是增删改查,都要强制刷新LRU队列在NSUserDefault的缓存。

API设计

API面向业务更加友好,回调方式采用block,功能包括GET、POST、下载、单文件上传、多文件上传、请求管理、缓存管理

GET请求

/**
 *  GET请求
 *
 *  @param url              请求路径
 *  @param cache            是否缓存
 *  @param refresh          是否刷新请求(遇到重复请求,若为YES,则会取消旧的请求,用新的请求,若为NO,则忽略新请   求,用旧请求)
 *  @param params           拼接参数
 *  @param progressBlock    进度回调
 *  @param successBlock     成功回调
 *  @param failBlock        失败回调
 *
 *  @return 返回的对象中可取消请求
 */
+ (YQURLSessionTask *)getWithUrl:(NSString *)url
                  refreshRequest:(BOOL)refresh
                           cache:(BOOL)cache
                          params:(NSDictionary *)params
                   progressBlock:(YQGetProgress)progressBlock
                    successBlock:(YQResponseSuccessBlock)successBlock
                       failBlock:(YQResponseFailBlock)failBlock;

POST请求

/**
 *  POST请求
 *
 *  @param url              请求路径
 *  @param cache            是否缓存
 *  @param refresh          解释同上
 *  @param params           拼接参数
 *  @param progressBlock    进度回调
 *  @param successBlock     成功回调
 *  @param failBlock        失败回调
 *
 *  @return 返回的对象中可取消请求
 */
+ (YQURLSessionTask *)postWithUrl:(NSString *)url
                   refreshRequest:(BOOL)refresh
                            cache:(BOOL)cache
                           params:(NSDictionary *)params
                    progressBlock:(YQPostProgress)progressBlock
                     successBlock:(YQResponseSuccessBlock)successBlock
                        failBlock:(YQResponseFailBlock)failBlock;

下载请求

/**
 *  文件下载
 *
 *  @param url           下载文件接口地址
 *  @param progressBlock 下载进度
 *  @param successBlock  成功回调
 *  @param failBlock     下载回调
 *
 *  @return 返回的对象可取消请求
 */
+ (YQURLSessionTask *)downloadWithUrl:(NSString *)url
                        progressBlock:(YQDownloadProgress)progressBlock
                         successBlock:(YQDownloadSuccessBlock)successBlock
                            failBlock:(YQDownloadFailBlock)failBlock;

文件上传

/**
 *  文件上传
 *
 *  @param url              上传文件接口地址
 *  @param data             上传文件数据
 *  @param type             上传文件类型
 *  @param name             上传文件服务器文件夹名
 *  @param mimeType         mimeType
 *  @param progressBlock    上传文件路径
 *  @param successBlock     成功回调
 *  @param failBlock        失败回调
 *
 *  @return 返回的对象中可取消请求
 */
+ (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         数据集合
 *  @param type          类型
 *  @param name          服务器文件夹名
 *  @param mimeType      mimeTypes
 *  @param progressBlock 上传进度
 *  @param successBlock  成功回调
 *  @param failBlock     失败回调
 *
 *  @return 任务集合
 */
+ (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;

获取当前正在运行的任务

/**
 *  正在运行的网络任务
 *
 *  @return 
 */
+ (NSArray *)currentRunningTasks;

取消所有网络请求

+ (void)cancleAllRequest;

取消下载请求

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

获取缓存大小

/**
 *  获取缓存大小
 *
 *  @return 缓存大小
 */
+ (NSUInteger)totalCacheSize;

清理缓存

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

自动清理缓存

    //每次网络请求的时候,检查此时磁盘中的缓存大小,阈值默认是40MB,如果超过阈值,则清理LRU缓
    //存,同时也会清理过期缓存,缓存默认SSL是7天,磁盘缓存的大小和SSL的设置可以通过该方法
    //[YQCacheManager shareManager] setCacheTime: diskCapacity:]设置。
    
    [[YQCacheManager shareManager] clearLRUCache];

源码地址

https://github.com/huangyingqiu/YQNetworking,喜欢的给个star!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 前言 最近一直在思考一个问题,网络层的要如何设计? 纯粹的网络层 何为纯粹呢?就是服务器返回什么数据,回调给业务层...
    欣东阅读 1,353评论 4 31
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,565评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 偷懒不是一种爱好,它只是人对事物一种本能的逃避,尤其是在比较困难的事物面前。人往往都会有这样的一种情绪,在...
    苦中甘阅读 295评论 1 3
  • [咖啡]最近很火的一首小诗: 每个人都有自己的时间轴 纽约时间比加州时间早三个小时, New York is 3 ...
    swiftlee阅读 11,328评论 0 2