美文网首页
010-YapDatabase 食用指南

010-YapDatabase 食用指南

作者: Yasic | 来源:发表于2017-11-26 19:21 被阅读164次

    YapDatabase 是一个工作在 iOS 和 MAC 上的数据库,有两大主要特性:

    • 基于 sqlite 建立的 collection/key/value/metadata 基础存储功能
    • 提供类似视图、辅助索引、全文搜索等高级功能的插件

    同时还有以下特性

    • 并发性:可以同时在多个连接上进行读写操作,不会阻塞主线程
    • 内建缓存:相比于 sqlite 的原始字节缓存,YapDatabase 的缓存可以跳过序列化和反序列化阶段
    • 集合:支持集合存储
    • 元数据 metadata:支持元数据存储,可以存储一些 object 的额外信息,比如时间戳等
    • 性能:在主线程获取数千对象不会掉帧
    • objectivec API
    • 拓展:内置拓展架构,同时支持自定义
    • 视图:YapDatabase 内置的 view 使得过滤、组合和排序数据非常便捷
    • 辅助索引:通过索引重要属性来加快搜索速度
    • 全文搜索:基于 sqlite 的 FTS 模块,可以以最小代价获得极速的搜索

    并发行

    YapDatabase 中的只读连接会保存数据库的即时快照,即使其他连接改变数据,也不会影响当前的连接。但必须遵循以下规则:

    • 可以同时建立多个连接 Connection
    • 每一个连接都是线程安全的
    • 可以同时拥有多个只读事务而不阻塞
    • 可以同时拥有多个只读事务和一个读写事务而不阻塞
    • 对于每一个数据库,同一时间只能有一个读写事务,有唯一一个串行duilie执行读写事务
    • 对于每一个连接,同一时间只能有一个事务,每一个连接都维护一个串行队列执行事务

    存储

    YapDatabase 支持任何类型的 object,只要设置好序列化和反序列化流程就可以,YapDatabase 有提供默认的序列化和反序列化流程,当然也支持自定义。对于支持了 NSCoding 协议的类,可以不需要额外的设置,比如 Cocoa 中的大多数内置类

    • NSString
    • NSNumber
    • NSArray
    • NSDictionary
    • NSSet
    • NSData
    • UIColor
    • UIImage

    对于自定义类,只需要实现 NSCoding 的序列化、反序列化方法就可以了。

    @interface Person : NSObject<NSCoding>
    
    @property(strong, nonatomic) NSString *name;
    @property(strong, nonatomic) NSString *gender;
    @property(assign, nonatomic) NSInteger age;
    
    @end
    
    @implementation Person
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        if ([super init])
        {
            self.name = [aDecoder decodeObjectForKey:@"name"];
            self.gender = [aDecoder decodeObjectForKey:@"gender"];
            self.age = [aDecoder decodeIntegerForKey:@"age"];
        }
        return self;
    }
    
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        [aCoder encodeObject:self.name forKey:@"name"];
        [aCoder encodeObject:self.gender forKey:@"gender"];
        [aCoder encodeInteger:self.age forKey:@"age"];
    }
    
    @end
    

    如果序列化类的某个属性也是支持 NSCoding 的类,则也可以直接存进数据库,同样的,对于一个数组,序列化和反序列化信息也会依次发送给每一个成员。

    YapDatabase 包含一些开箱即用的序列化函数

    /**
    默认的序列化/反序列化函数,实现了 NSCoding 协议,任何支持 NSCoding 协议的对象(包括系统自带的大多数类)都可以被序列化/反序列化。
    **/
    + (YapDatabaseSerializer)defaultSerializer;
    + (YapDatabaseDeserializer)defaultDeserializer;
    
    /**
    属性列表序列化/反序列化函数,只支持 SData, NSString, NSArray, NSDictionary, NSDate, NSNumber 这些类,采取了一些优化措施
    **/
    + (YapDatabaseSerializer)propertyListSerializer;
    + (YapDatabaseDeserializer)propertyListDeserializer;
    
    /**
    一个针对 NSDate 对象的快速序列化/反序列化函数
    **/
    + (YapDatabaseSerializer)timestampSerializer;
    + (YapDatabaseDeserializer)timestampDeserializer;
    

    自定义序列化/反序列化函数的方法可以用于加密、压缩和性能优化等方面。

    typedef NSData* (^YapDatabaseSerializer)(NSString *collection, NSString *key, id object);
    typedef id (^YapDatabaseDeserializer)(NSString *collection, NSString *key, NSData *data);
    

    集合

    集合为众多拥有同一关键属性的元素提供了更方拜年的存储结构,它为元素提供除了 key 值外另一个层次的标识,所以 YapDatabase 也可以理解为是 “字典的字典”。

    YapDatabase 关于集合的 API 有以下几个

    /**
     * Returns the total number of collections.
     * Each collection may have 1 or more key/object pairs.
    **/
    - (NSUInteger)numberOfCollections;
    
    /**
     * Returns the total number of keys in the given collection.
     * Returns zero if the collection doesn't exist (or all key/object pairs from the collection have been removed).
    **/
    - (NSUInteger)numberOfKeysInCollection:(NSString *)collection;
    
    /**
     * Object access.
     * Objects are automatically deserialized using database's configured deserializer.
    **/
    - (id)objectForKey:(NSString *)key inCollection:(NSString *)collection;
    
    /**
     * Fast enumeration over all keys in the given collection.
     *
     * This uses a "SELECT key FROM database WHERE collection = ?" operation,
     * and then steps over the results invoking the given block handler.
    **/
    - (void)enumerateKeysInCollection:(NSString *)collection
                           usingBlock:(void (^)(NSString *key, BOOL *stop))block;
    

    可以看到一个 object 在 YapDatabase 中是依靠 collection 和 key 两个标识共同唯一确定的。

    缓存

    YapDatabase 的每一个连接都有自己专属的数据库缓存,与 sqlite 的二进制数据缓存层相比,YapDatabase 的缓存层直接缓存 objectivec 对象,减少了序列化和反序列化的开销。

    缓存默认打开,大小为 250,以下是 API

    // 这一属性可以选择关闭或开启缓存
    @property (atomic, assign, readwrite) BOOL objectCacheEnabled;
    @property (atomic, assign, readwrite) BOOL metadataCacheEnabled;
    
    // 可以设置缓存大小,如果赋值为 0 则缓存空间无限
    @property (atomic, assign, readwrite) NSUInteger objectCacheLimit;
    @property (atomic, assign, readwrite) NSUInteger metadataCacheLimit;
    

    支持对象与元数据分开缓存,也支持在运行中进行缓存大小的修改,每一个连接的缓存都会自动与数据库进行同步。

    元数据 Metedata

    YapDatabase 支持存储的元祖不仅仅包含对象,还有元数据,底层的数据库表也为元数据开辟了独立的存储列。对象是必须有的,但是元数据是可选的,如果对象为 nil 则元组会被移除,但是元数据为 nil 并不会。元数据与对象可以拥有独立的缓存机制和序列化/反序列化函数。

    元数据相关的存储与更新 API 有以下这些

    /**
     * Invokes setObject:forKey:inCollection:withMetadata:,
     * and passes a nil value for the metadata parameter.
    **/
    - (void)setObject:(nullable id)object
               forKey:(NSString *)key
         inCollection:(nullable NSString *)collection;
    
    /**
     * If you call this method with a nil object, then it will delete the row.
     * (Equivalent to calling removeObjectForKey:inCollection:)
     * 
     * Otherwise, this method inserts/updates the row,
     * and sets BOTH the object & metadata columns to the given values.
    **/
    - (void)setObject:(nullable id)object
               forKey:(NSString *)key
         inCollection:(nullable NSString *)collection
         withMetadata:(nullable id)metadata;
    
    /**
     * If a row with the given collection/key already exists,
     * then this method updates ONLY the object value.
     * The metadata value for the row isn't touched. (It remains whatever it was before.)
     * 
     * Again, it's not possible to have a nil object for a row.
     * So if you try to set the object to nil, this is just going to delete the row.
    **/
    - (void)replaceObject:(nullable id)object
                   forKey:(NSString *)key
             inCollection:(nullable NSString *)collection;
    
    /**
     * If a row with the given collection/key already exists,
     * then this method updates ONLY the metadata value.
     * The object value for the row isn't touched. (It remains whatever it was before.)
    **/
    - (void)replaceMetadata:(nullable id)metadata
                     forKey:(NSString *)key
               inCollection:(nullable NSString *)collection;
    

    最佳实践

    • 避免用同一个连接在主线程和其他线程同时执行事务,因为连接只能串行执行事务
    • 避免创建太多连接,会带来开销问题,同时会降低缓存命中,因为连接中执行的事务少,缓存性能没有表现出来
    • 为主线程使用专用的连接
    • 在主线程的专用连接上不执行任何读写事务(ReadWrite transaction),只执行只读事务
    • 为读写操作建立单独的连接

    相关 API 与操作

    1. 删除

    • 删除指定集合的所有元素

      - (void)removeAllObjectsInCollection:(NSString *)collection
      
    • 删除数据库所有元素

      - (void)removeAllObjectsInAllCollections;
      
    • 删除某一个指定元素

      - (void)removeObjectForKey:(NSString *)key inCollection:(nullable NSString *)collection;
      
    • 删除某一些指定元素

      - (void)removeObjectsForKeys:(NSArray<NSString *> *)keys inCollection:(nullable NSString *)collection;
      

    2. 获取总数

    • 获取集合总数

      - (NSUInteger)numberOfCollections;
      
    • 获取集合中 key 总数

      - (NSUInteger)numberOfKeysInCollection:(nullable NSString *)collection;
      
    • 获取数据库中 key 总数

      - (NSUInteger)numberOfKeysInAllCollections;
      

    3. 获取列表

    • 获取所有集合的列表

      - (NSArray<NSString *> *)allCollections;
      
    • 获取某个集合中所有 key 的列表

      - (NSArray<NSString *> *)allKeysInCollection:(nullable NSString *)collection;
      

    4. 获取对象与元数据

    • 获取指定对象

      - (nullable id)objectForKey:(NSString *)key inCollection:(nullable NSString *)collection;
      
    • 检测指定对象是否在集合中

      - (BOOL)hasObjectForKey:(NSString *)key inCollection:(nullable NSString *)collection;
      
    • 获取指定对象和元数据到指定地址,存在该 key 则返回 YES,否则返回 NO

      - (BOOL)getObject:(__nullable id * __nullable)objectPtr
               metadata:(__nullable id * __nullable)metadataPtr
                 forKey:(NSString *)key
           inCollection:(nullable NSString *)collection;
      
    • 获取指定元数据

      - (nullable id)metadataForKey:(NSString *)key inCollection:(nullable NSString *)collection;
      

    5. 获取原始数据

    下面的方法会跳过 oc 层缓存,直接获取数据库中的原始数据,因此速度不如上面的方法。

    • 获取指定对象

      - (nullable NSData *)serializedObjectForKey:(NSString *)key inCollection:(nullable NSString *)collection;
      
    • 获取指定元数据

      - (nullable NSData *)serializedMetadataForKey:(NSString *)key inCollection:(nullable NSString *)collection;
      
    • 读取指定对象和元数据到指定地址

    - (BOOL)getSerializedObject:(NSData * __nullable * __nullable)serializedObjectPtr
            serializedMetadata:(NSData * __nullable * __nullable)serializedMetadataPtr
                        forKey:(NSString *)key
                  inCollection:(nullable NSString *)collection;
    

    6. 枚举

    • 枚举集合

      - (void)enumerateCollectionsUsingBlock:(void (^)(NSString *collection, BOOL *stop))block;
      - (void)enumerateCollectionsForKey:(NSString *)key usingBlock:(void (^)(NSString *collection, BOOL *stop))block;
      
    • 枚举key

      - (void)enumerateKeysInCollection:(nullable NSString *)collection
                           usingBlock:(void (^)(NSString *key, BOOL *stop))block;
      - (void)enumerateKeysInAllCollectionsUsingBlock:(void (^)(NSString *collection, NSString *key, BOOL *stop))block;
      - (void)enumerateKeysAndObjectsInCollection:(nullable NSString *)collection
                                     usingBlock:(void (^)(NSString *key, id object, BOOL *stop))block;
      - (void)enumerateKeysAndMetadataInCollection:(nullable NSString *)collection
                                      usingBlock:(void (^)(NSString *key, __nullable id metadata, BOOL *stop))block;
      

    7. 存储和更新

    • 存储

      - (void)setObject:(nullable id)object forKey:(NSString *)key inCollection:(nullable NSString *)collection;
      - (void)setObject:(nullable id)object
               forKey:(NSString *)key
         inCollection:(nullable NSString *)collection
         withMetadata:(nullable id)metadata;
      
    • 更新

      - (void)replaceObject:(nullable id)object forKey:(NSString *)key inCollection:(nullable NSString *)collection;
      - (void)replaceMetadata:(nullable id)metadata forKey:(NSString *)key inCollection:(nullable NSString *)collection;
      

    这里要注意如果存储的对象所属 key 是已经存储在数据库的,则会自动更新这个元素,如果传递的对象是 nil,则会移除这个元素。

    相关文章

      网友评论

          本文标题:010-YapDatabase 食用指南

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