美文网首页iOS开发程序员编程知识点
使用FMDB做离线缓存的例子(iOS)

使用FMDB做离线缓存的例子(iOS)

作者: 訷訷 | 来源:发表于2016-08-10 11:54 被阅读2694次

    本文以仿微博的应用为基础,实现使用FMDB做离线缓存

    仿微博下拉
    仿微博上拉

    设计思路:

    分析加载微博过程:
    1. 尝试从沙盒加载缓存数据
    2. 有缓存,直接加载缓存
    3. 无缓存,发送请求,展示返回的数据,将数据存入沙盒


      加载微博过程.png
    分析微博返回数据:
    1. 需要加载的微博多,数据量大,不适合用plist和NSCoding这类一次性加载和存储全部数据的方法,使用数据库则可以做到取一部分数据和存一部分的数据
    2. 由于微博模型多,如果按照后台服务器一样,一个模型建一张表,每个表(如:用户,微博,图片,文字)又相互之间通过外键联系,会导致客户端的数据库过于复杂,所以只建一张表
    3. 每条微博的字段非常多,在客户端数据库表中也创建相应数量的字段保存数据是不适合的,所以可以在数据库表中只创建一个blob类型的status字段保存每条微博的全部数据
    4. 每条微博是根据自身字段idstr大小来比较新旧的,把每条微博从数据库取出再取出idstr来比较新旧是不适合的,所以表中还应该增加idstr字段方便查询
    5. 最终确定建一张表,三个字段:id(系统默认),status,idstr
    微博模型较多
    微博的字段非常多
    表结构

    实现步骤

    步骤1.创建一个工具类StatusTool

    步骤2.在StatusTool的initialize中初始化数据库

    static FMDatabase *_db;
    + (void)initialize
    {
        // 1.打开数据库
        NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"statuses.sqlite"];
        _db = [FMDatabase databaseWithPath:path];
        [_db open];
        
        // 2.创表
        [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_status (id integer PRIMARY KEY, status blob NOT NULL, idstr text NOT NULL);"];
    }```
    
    步骤3. StatusTool实现存储方法
    
    • (void)saveStatuses:(NSArray *)statuses
      {
      // 要将一个对象存进数据库的blob字段,最好先转为NSData
      // 一个对象要遵守NSCoding协议,实现协议中相应的方法,才能转成NSData
      for (NSDictionary *status in statuses) {
      // NSDictionary --> NSData
      NSData *statusData = [NSKeyedArchiver archivedDataWithRootObject:status];
      [_db executeUpdateWithFormat:@"INSERT INTO t_status(status, idstr) VALUES (%@, %@);", statusData, status[@"idstr"]];
      }
      }```

    步骤4. StatusTool实现取缓存方法

    + (NSArray *)statusesWithParams:(NSDictionary *)params
    {
        // 根据请求参数生成对应的查询SQL语句
        NSString *sql = nil;
        if (params[@"since_id"]) { // 下拉刷新
            sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr > %@ ORDER BY idstr DESC LIMIT 20;", params[@"since_id"]];
        } else if (params[@"max_id"]) { // 上拉刷新
            sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr <= %@ ORDER BY idstr DESC LIMIT 20;", params[@"max_id"]];
        }
        
        // 执行SQL
        FMResultSet *set = [_db executeQuery:sql];
        NSMutableArray *statuses = [NSMutableArray array];
        while (set.next) {
            NSData *statusData = [set objectForColumnName:@"status"];
            NSDictionary *status = [NSKeyedUnarchiver unarchiveObjectWithData:statusData];
            [statuses addObject:status];
        }
        return statuses;
    }```
    
    步骤5.方法调用:
    

    /**

    • 下拉刷新,加载最新的数据
      */
    • (void)loadNewStatus
      {
      // 1.拼接请求参数
      。。。。。。。
      params[@"since_id"] = firstStatusF.status.idstr;

      // 2.先尝试从数据库中加载微博数据
      NSArray statuses = [StatusTool statusesWithParams:params];
      if (statuses.count) { // 数据库有缓存数据
      /
      ……….处理数据, 展示返回的数据*/
      } else { // 数据库没缓存数据
      // 发送请求
      [HttpTool get:@"https://api.weibo.com/2/statuses/friends_timeline.json" params:params success:^(id json) {
      // 缓存新浪返回的字典数组
      [StatusTool saveStatuses:json[@"statuses"]];

            /* ……….处理数据 , 展示返回的数据*/
        } failure:^(NSError *error) {
            。。。。
        }];
      

      }
      }

    /**

    • 上拉刷新,加载更多的微博数据
      */
    • (void)loadMoreStatus
      {
      // 1.拼接请求参数
      。。。。。。。
      params[@"max_id"] = @(maxId);

      // 2. 先尝试从数据库中加载微博数据
      NSArray statuses = [StatusTool statusesWithParams:params];
      if (statuses.count) { // 数据库有缓存数据
      /
      ……….处理数据, 展示返回的数据*/
      } else { // 数据库没缓存数据
      // 发送请求
      [HttpTool get:@"https://api.weibo.com/2/statuses/friends_timeline.json" params:params success:^(id json) {
      // 缓存新浪返回的字典数组
      [StatusTool saveStatuses:json[@"statuses"]];

            /* ……….处理数据, 展示返回的数据*/
        } failure:^(NSError *error) {
            。。。。。。
        }];
      

      }
      }```

    遇到问题:

    如果把NSDictionary字典数据status直接通过[_db executeUpdateWithFormat:@"INSERT INTO t_status(status, idstr) VALUES (%@, %@);", status, status[@"idstr"]];来存储,就会取不出来。
    原因是通过%@来传入字典对象,相当于传入[status description];,打印类型可以发现取出的是字符串,不是我们预期的字典对象。
    解决方法:
    把字典用NSData *statusData = [NSKeyedArchiver archivedDataWithRootObject:status];转成NSData再存入数据库。
    取出数据时,用NSDictionary *status = [NSKeyedUnarchiver unarchiveObjectWithData:statusData];再转成字典。

    为什么字典对象能转成NSData类型

    字典对象能转成NSData类型的本质是遵守了NSCoding协议,所以如果想要把自定义对象转成NSData类型需要遵守NSCoding协议,实现encodeWithCoder和initWithCoder方法

    例:自定义对象

    @interface Shop : NSObject <NSCoding>
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) double  price;
    @end
    
    @implementation Shop
    - (void)encodeWithCoder:(NSCoder *)encoder
    {
        [encoder encodeObject:self.name forKey:@"name"];
        [encoder encodeDouble:self.price forKey:@"price"];
    }
    
    - (id)initWithCoder:(NSCoder *)decoder
    {
        if (self = [super init]) {
            self.name = [decoder decodeObjectForKey:@"name"];
            self.price = [decoder decodeDoubleForKey:@"price"];
        }
        return self;
    }
    @end```

    相关文章

      网友评论

      • 广东吃瓜大队队长:楼主,问个问题,就是第二天有新的数据,我缓存了进去,但是最新的数据在旧的数据之后,怎么把最新的数据放到旧的数据前
      • 63333aa9cb00:楼主你好,就是如果缓存里面有数据,这个时候数据库跟新了几条新的数据,这时候加载的还是之前缓存的数据,想实现如果有新数据就加载最新的数据,有什么方法可以实现吗
        63333aa9cb00:@訷訷 好的 到时候最好一起上个demo。:yum:
        訷訷:没错,其实我这里下拉加载新数据逻辑是有问题的,但我一直没把这篇文章改过来。
        我工作中真实的项目处理是:先直接展示缓存数据,然后执行下拉刷新,下拉刷新里就直接网络请求最新数据,请求成功直接删除数据库所有旧数据,再缓存这次的新数据,展示新数据。其实一般普通的应用差不多就是这样处理的。
        以后我可能会在文章中详细补充这个问题。
      • 63333aa9cb00:楼主你好,请问有demo吗
      • 逆流向上:大神,求demo一份,1552349098@qq.com
      • Dicky信徒:其实还是有问题的,每次进来其实加载的不是最新的几条微博,还是缓存微博里比较新的微博。
      • 本尊专属:能给我一个demo吗 1368618321@qq.com 谢谢啦:hibiscus:
      • 小城东风:伸手党都这么厉害了...
      • First灬DKS:博主,我表示十分想看这个代码,请问有Demo吗?870815325@qq.com 谢谢了!
      • 亊已過無需罶戀:求demo 15155317656@163.com
      • 青鸟evergreen:黑马的代码
        MrBeave丶彬彬:@訷訷 有demo吗 1107385382@qq.com 求一份
        4dfd968b6289:@訷訷 求分享 @1870398445@qq.com
        訷訷:@青鸟evergreen 你懂的:smile:
      • ForKid:滑动很快的话会不会影响性能啊
        小城东风:@ForKid 不会...

      本文标题:使用FMDB做离线缓存的例子(iOS)

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