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,则会移除这个元素。
网友评论