iOS开发-网络缓存封装

作者: 向钱冲啊 | 来源:发表于2016-10-16 23:05 被阅读519次

    iOS开发中网络缓存苹果已经提供了比较好用的NSURLCache类,但是只支持GET请求,所以抛去原生的网络缓存类,就想写一个较为好用的网络缓存封装。
    本文是基于数据库FMDB来完成实现的,并无技术深度,只当作为大家共同进步道路上的一个思路。如Demo中有任何疑问,大家可以提问哈。
    实现思路由看了标哥的HYBNetworking源码提供,标哥是在本地建文件夹实现的缓存,我是基于数据库,都是一样的道理。

    依赖的三方库

    首先,实现网络缓存之前,需要用到的第三方库有FMDB ,AFNetworking,这两个大家都很熟悉了,就不说了。
    重点是这个基于FMDB封装的key-value存储框架 YTKKeyValueStore,该库是出自猿题库公司开源的轻量级存储框架,源码也就400多行。但是这种存储方式足以满足各大中小型APP了。关于怎么使用,并无学习成本。看该库的GitHub链接,1分钟就能上手!

    实现逻辑

    学会了怎么使用,接下来结合代码,简单说下实现思路:

    1.创建类继承自AFHTTPSessionManager,然后根据load方法的妙用,自动监测网络状态,获取网络状态后对其处理,有网加载,没网本地取出。

    + (void)load
    {
        //设置网络请求的基本属性
        JMHttpRequestMethod *httpMethod;
        httpMethod.requestSerializer = [AFJSONRequestSerializer serializer];
        //设置请求的超时时间
        httpMethod.requestSerializer.timeoutInterval = 20.f;
        //设置服务器返回结果的类型:JSON (AFJSONResponseSerializer,AFHTTPResponseSerializer)
        httpMethod.responseSerializer = [AFJSONResponseSerializer serializer];
        
        httpMethod.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:
                                                                @"application/json",
                                                                @"text/html",
                                                                @"text/json",
                                                                @"text/plain",
                                                                @"text/javascript",
                                                                @"text/xml", @"image/*", nil];
        //创建数据库和表
        _store = [[YTKKeyValueStore alloc] initDBWithName:httpCache];
        [_store createTableWithName:httpCache];
       //开始监测网络状态
          [self startMonitoringNetworkStatus];
        
    }
    
    /**
     监测网络状态
     */
    + (void)startMonitoringNetworkStatus
    {
            AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
            [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
                switch (status)
                {
                    case AFNetworkReachabilityStatusUnknown:
                     //设置全局BOOL变量 获取网络状态
                        _isHasNetWork = NO;
                        _status = JMNetworkStatusUnknown;
                        break;
                    case AFNetworkReachabilityStatusNotReachable:
                        _isHasNetWork = NO;
                        _status = JMNetworkStatusNotNetWork;
                        NSLog(@"没有网的状态");
                        break;
                    case AFNetworkReachabilityStatusReachableViaWWAN:
                        _isHasNetWork = YES;
                        _status = JMNetworkStatusReachableViaWWAN;
                        break;
                    case AFNetworkReachabilityStatusReachableViaWiFi:
                        _isHasNetWork = YES;
                        _status = JMNetworkStatusReachableViaWiFi;
                        NSLog(@"现在是有网状态");
                        break;
                }
            }];
            [manager startMonitoring];
    }
    
    /**
     获取当前的网络状态
     
     @return YES 有网  NO 没有联网
     */
    +(BOOL)getCurrentNetWorkStatus
    {
        return _isHasNetWork;
    }
    

    2.GETPOST请求接口添加布尔属性,供选择是否选择对该URL缓存

    /**
     GET 请求 带有进度回调的 API
    
     @param url          请求的url
     @param refreshCache 是否对该页面进行缓存
     @param params       请求数据向服务器传的参数
     @param progress     请求进度回调
     @param success      请求成功回调
     @param fail         请求失败回调
    
     @return self
     */
    + (JMHttpRequestMethod *)getWithUrl:(NSString *)url
                           refreshCache:(BOOL)refreshCache
                                 params:(NSDictionary *)params
                               progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress
                                success:(void(^)(id responseObject))success
                                   fail:(void(^)(NSError *error))fail;
    
    /**
     POST 请求 带有进度回调的 API
     
     @param url               请求的url
     @param refreshCache 是否对该页面进行缓存
     @param params       请求数据向服务器传的参数
     @param progress     请求进度回调
     @param success      请求成功回调
     @param fail         请求失败回调
     
     @return self
     */
    + (JMHttpRequestMethod *)postWithUrl:(NSString *)url
                            refreshCache:(BOOL)refreshCache
                                  params:(NSDictionary *)params
                                progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress
                                 success:(void(^)(id responseObject))success
                                    fail:(void(^)(NSError *error))fail; ```
    
    3.每次调用请求接口,先在本地数据库读取缓存,如果本地没有再进行请求,请求完之后对其缓存。这里以```GET ```请求为例, ```POST ```请求也一样的逻辑。
    

    /**
    GET 请求 带有进度回调的 API

    @param url 请求的url
    @param refreshCache 是否对该页面进行缓存
    @param params 请求数据向服务器传的参数
    @param progress 请求进度回调
    @param success 请求成功回调
    @param fail 请求失败回调

    @return self
    */

    • (JMHttpRequestMethod *)getWithUrl:(NSString *)url
      refreshCache:(BOOL)refreshCache
      params:(NSDictionary *)params
      progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress
      success:(void(^)(id responseObject))success
      fail:(void(^)(NSError *error))fail
      {
      JMHttpRequestMethod *request = nil;
      //先获取当前的网络状态有网的话,进行请求
      if ([JMHttpRequestMethod getCurrentNetWorkStatus]) {
      //判断是否对该url进行缓存
      if (!refreshCache) {
      [self requestNotCacheWithHttpMethod:0 url:url params:params progress:progress success:success fail:fail];
      }else {
      //如果进行缓存,先从本地取出缓存的数据
      NSDictionary *dict = [_store getObjectById:url fromTable:httpCache];
      //如果本地有数据,直接回调,否则进行网络请求
      if (dict) {
      success(dict);
      }else {
      [[self manager] GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
      progress(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount);
      } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
      // 请求到数据后就进行存储,然后回调请求成功的数据
      [_store putObject:responseObject withId:url intoTable:httpCache];
      success(responseObject);
      NSLog(@"\nRequest success, URL: %@\n params:%@\n response:%@\n\n",url,params,responseObject);
      } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
      fail(error);
      NSLog(@"error = %@",error.description);
      }];
      }
      }
      } else {
      //当前没有网络,然后从本地数据库中取出数据
      NSDictionary *dict = [_store getObjectById:url fromTable:httpCache];
      //本地有数据的话,进行回调,否则取出失败打印信息
      if (dict) {
      success(dict);
      }else {
      NSLog(@"当前为无网络状态,本地也没有缓存数据");
      }
      }
      return request;

    /**
    不进行缓存时,进行网络请求的方法

    @param httpMethod 0 : 代表GET请求 1:代表POST请求
    @param url 请求的url
    @param params 请求参数
    @param progress 请求进度回调
    @param success 请求成功回调
    @param fail 请求失败回调
    */

    • (void)requestNotCacheWithHttpMethod:(NSInteger)httpMethod
      url:(NSString *)url
      params:(NSDictionary *)params
      progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress
      success:(void(^)(id responseObject))success
      fail:(void(^)(NSError *error))fail
      {
      if (httpMethod == 0) {
      [[self manager]GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
      progress(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount);
      } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
      success(responseObject);
      NSLog(@"\nRequest success, URL: %@\n params:%@\n response:%@\n\n",url,params,responseObject);
      } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
      fail(error);
      }];
      }else {
      [[self manager ]POST:url parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
      progress(uploadProgress.completedUnitCount, uploadProgress.totalUnitCount);
      } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
      success(responseObject);
      NSLog(@"\nRequest success, URL: %@\n params:%@\n response:%@\n\n",url,params,responseObject);
      } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
      fail(error);
      }];
      }
      }
    
    4.提供清除缓存和获取文件大小接口
    

    /**
    获取网络缓存 文件大小

    @return size 直接返回字符串,单位M。保留两位小数。举例 1.12M
    */

    • (NSString )fileSizeWithDBPath
      {
      NSFileManager
      manager = [NSFileManager defaultManager];
      if ([manager fileExistsAtPath:[PATH_OF_NetWork stringByAppendingPathComponent:httpCache]]){
      unsigned long long fileSize = [[manager attributesOfItemAtPath:[PATH_OF_NetWork stringByAppendingPathComponent:httpCache] error:nil] fileSize];
      NSString *size = [NSString stringWithFormat:@"%.2fM",fileSize/1024.0/1024.0];
      return size;
      }else {
      return @"0M";
      }
      return 0;
      }

    /**
    清除所有网络缓存
    */

    • (void)cleanNetWorkRefreshCache
      {
      NSError *error;
      BOOL isSuccess = [[NSFileManager defaultManager]removeItemAtPath:[PATH_OF_NetWork stringByAppendingPathComponent:httpCache] error:&error];
      if (isSuccess) {
      NSLog(@"clean cache file is success");
      }else {
      if ([PATH_OF_NetWork stringByAppendingPathComponent:httpCache]) {
      NSLog(@"error:%@",error.description);
      }else {
      NSLog(@"error: cache file is not exist");
      }
      }
      }
    
    
    本文大致实现逻辑就是这样,如果哪些不对的地方,可以提问哈。
    好久没更新了,今后经常更新下,记录下自己所遇到的坑,这样才能进步!
    [详情请见本文github链接](https://github.com/JimmyLession/JMRequestNetWorkCache)

    相关文章

      网友评论

      • 动感超人丶:如果有下拉刷新,缓存怎么处理啊?
        动感超人丶:@向钱冲啊 加下我qq 38020858,请教下
        动感超人丶:@向钱冲啊 我意思是,一般下拉刷新,每条信息会有sinceid或者page等字段 做分页。这个框架是把数据整体data存入一个字段 而不是 把数据解析拆分,有什么好办法吗?
        向钱冲啊:@动感超人丶猿题库公司这个框架存储数据时候有个过期时间的接口。你可以获取到后针对性对某些界面设置个2分钟清除下缓存。。。如果你特别在意这两分钟,那么最好的办法就是不做缓存,动态性太强没必要了。。。就简书而言,没网时候也是一团糟啥也木有
      • 动感超人丶:YTKKeyValueStore 不改写成单利吗?
        向钱冲啊:@动感超人丶 哈哈,当然是单利,我只是单独把这个网络类抽出来的。项目中我有单独的数据库操作单利工具类
      • 马爷:我想问一下 如果缓存有的话 是不是就不需要去请求网络了吗?
        向钱冲啊:@马爷 恩,是的啊。我注释打好了啊
        //先获取当前的网络状态有网的话,进行请求
        if ([JMHttpRequestMethod getCurrentNetWorkStatus]) {
        //判断是否对该url进行缓存
        if (!refreshCache) {
        [self requestNotCacheWithHttpMethod:0 url:url params:params progress:progress success:success fail:fail];
        }else {
        //如果进行缓存,先从本地取出缓存的数据
        NSDictionary *dict = [_store getObjectById:url fromTable:httpCache];
        //如果本地有数据,直接回调,否则进行网络请求
        if (dict) {
        // 你说的就是这一步,本地取出的字典有值,直接回调字典
        success(dict);
        }else {
        //然后这时候再去网络请求。
        }
        马爷:额 我的意思是 只要有缓存 就不再去服务器请求数据了吗
        向钱冲啊:@马爷 是的。文中实现逻辑第一步,就已经获取到网络状态了,第二步请求之前就是先判断网络状态。没网走的else你看看代码。。。是直接数据库取了

      本文标题:iOS开发-网络缓存封装

      本文链接:https://www.haomeiwen.com/subject/qzkayttx.html