YapDatabase 是一個(gè)工作在 iOS 和 MAC 上的數(shù)據(jù)庫,有兩大主要特性:
- 基于 sqlite 建立的 collection/key/value/metadata 基礎(chǔ)存儲(chǔ)功能
- 提供類似視圖、輔助索引屠列、全文搜索等高級(jí)功能的插件
同時(shí)還有以下特性
- 并發(fā)性:可以同時(shí)在多個(gè)連接上進(jìn)行讀寫操作禽笑,不會(huì)阻塞主線程
- 內(nèi)建緩存:相比于 sqlite 的原始字節(jié)緩存,YapDatabase 的緩存可以跳過序列化和反序列化階段
- 集合:支持集合存儲(chǔ)
- 元數(shù)據(jù) metadata:支持元數(shù)據(jù)存儲(chǔ)焚刚,可以存儲(chǔ)一些 object 的額外信息屈梁,比如時(shí)間戳等
- 性能:在主線程獲取數(shù)千對(duì)象不會(huì)掉幀
- objectivec API
- 拓展:內(nèi)置拓展架構(gòu)嗤练,同時(shí)支持自定義
- 視圖:YapDatabase 內(nèi)置的 view 使得過濾、組合和排序數(shù)據(jù)非常便捷
- 輔助索引:通過索引重要屬性來加快搜索速度
- 全文搜索:基于 sqlite 的 FTS 模塊在讶,可以以最小代價(jià)獲得極速的搜索
并發(fā)行
YapDatabase 中的只讀連接會(huì)保存數(shù)據(jù)庫的即時(shí)快照煞抬,即使其他連接改變數(shù)據(jù),也不會(huì)影響當(dāng)前的連接真朗。但必須遵循以下規(guī)則:
- 可以同時(shí)建立多個(gè)連接 Connection
- 每一個(gè)連接都是線程安全的
- 可以同時(shí)擁有多個(gè)只讀事務(wù)而不阻塞
- 可以同時(shí)擁有多個(gè)只讀事務(wù)和一個(gè)讀寫事務(wù)而不阻塞
- 對(duì)于每一個(gè)數(shù)據(jù)庫,同一時(shí)間只能有一個(gè)讀寫事務(wù)僧诚,有唯一一個(gè)串行duilie執(zhí)行讀寫事務(wù)
- 對(duì)于每一個(gè)連接遮婶,同一時(shí)間只能有一個(gè)事務(wù),每一個(gè)連接都維護(hù)一個(gè)串行隊(duì)列執(zhí)行事務(wù)
存儲(chǔ)
YapDatabase 支持任何類型的 object湖笨,只要設(shè)置好序列化和反序列化流程就可以旗扑,YapDatabase 有提供默認(rèn)的序列化和反序列化流程,當(dāng)然也支持自定義慈省。對(duì)于支持了 NSCoding 協(xié)議的類臀防,可以不需要額外的設(shè)置,比如 Cocoa 中的大多數(shù)內(nèi)置類
- NSString
- NSNumber
- NSArray
- NSDictionary
- NSSet
- NSData
- UIColor
- UIImage
對(duì)于自定義類边败,只需要實(shí)現(xiàn) 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
如果序列化類的某個(gè)屬性也是支持 NSCoding 的類笑窜,則也可以直接存進(jìn)數(shù)據(jù)庫致燥,同樣的,對(duì)于一個(gè)數(shù)組持搜,序列化和反序列化信息也會(huì)依次發(fā)送給每一個(gè)成員辫红。
YapDatabase 包含一些開箱即用的序列化函數(shù)
/**
默認(rèn)的序列化/反序列化函數(shù),實(shí)現(xiàn)了 NSCoding 協(xié)議肄满,任何支持 NSCoding 協(xié)議的對(duì)象(包括系統(tǒng)自帶的大多數(shù)類)都可以被序列化/反序列化脱吱。
**/
+ (YapDatabaseSerializer)defaultSerializer;
+ (YapDatabaseDeserializer)defaultDeserializer;
/**
屬性列表序列化/反序列化函數(shù)智政,只支持 SData, NSString, NSArray, NSDictionary, NSDate, NSNumber 這些類,采取了一些優(yōu)化措施
**/
+ (YapDatabaseSerializer)propertyListSerializer;
+ (YapDatabaseDeserializer)propertyListDeserializer;
/**
一個(gè)針對(duì) NSDate 對(duì)象的快速序列化/反序列化函數(shù)
**/
+ (YapDatabaseSerializer)timestampSerializer;
+ (YapDatabaseDeserializer)timestampDeserializer;
自定義序列化/反序列化函數(shù)的方法可以用于加密箱蝠、壓縮和性能優(yōu)化等方面续捂。
typedef NSData* (^YapDatabaseSerializer)(NSString *collection, NSString *key, id object);
typedef id (^YapDatabaseDeserializer)(NSString *collection, NSString *key, NSData *data);
集合
集合為眾多擁有同一關(guān)鍵屬性的元素提供了更方拜年的存儲(chǔ)結(jié)構(gòu),它為元素提供除了 key 值外另一個(gè)層次的標(biāo)識(shí)抡锈,所以 YapDatabase 也可以理解為是 “字典的字典”疾忍。
YapDatabase 關(guān)于集合的 API 有以下幾個(gè)
/**
* 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;
可以看到一個(gè) object 在 YapDatabase 中是依靠 collection 和 key 兩個(gè)標(biāo)識(shí)共同唯一確定的。
緩存
YapDatabase 的每一個(gè)連接都有自己專屬的數(shù)據(jù)庫緩存床三,與 sqlite 的二進(jìn)制數(shù)據(jù)緩存層相比一罩,YapDatabase 的緩存層直接緩存 objectivec 對(duì)象,減少了序列化和反序列化的開銷撇簿。
緩存默認(rèn)打開聂渊,大小為 250,以下是 API
// 這一屬性可以選擇關(guān)閉或開啟緩存
@property (atomic, assign, readwrite) BOOL objectCacheEnabled;
@property (atomic, assign, readwrite) BOOL metadataCacheEnabled;
// 可以設(shè)置緩存大小四瘫,如果賦值為 0 則緩存空間無限
@property (atomic, assign, readwrite) NSUInteger objectCacheLimit;
@property (atomic, assign, readwrite) NSUInteger metadataCacheLimit;
支持對(duì)象與元數(shù)據(jù)分開緩存汉嗽,也支持在運(yùn)行中進(jìn)行緩存大小的修改,每一個(gè)連接的緩存都會(huì)自動(dòng)與數(shù)據(jù)庫進(jìn)行同步找蜜。
元數(shù)據(jù) Metedata
YapDatabase 支持存儲(chǔ)的元祖不僅僅包含對(duì)象饼暑,還有元數(shù)據(jù),底層的數(shù)據(jù)庫表也為元數(shù)據(jù)開辟了獨(dú)立的存儲(chǔ)列洗做。對(duì)象是必須有的弓叛,但是元數(shù)據(jù)是可選的,如果對(duì)象為 nil 則元組會(huì)被移除诚纸,但是元數(shù)據(jù)為 nil 并不會(huì)撰筷。元數(shù)據(jù)與對(duì)象可以擁有獨(dú)立的緩存機(jī)制和序列化/反序列化函數(shù)。
元數(shù)據(jù)相關(guān)的存儲(chǔ)與更新 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;
最佳實(shí)踐
- 避免用同一個(gè)連接在主線程和其他線程同時(shí)執(zhí)行事務(wù)畦徘,因?yàn)檫B接只能串行執(zhí)行事務(wù)
- 避免創(chuàng)建太多連接毕籽,會(huì)帶來開銷問題,同時(shí)會(huì)降低緩存命中井辆,因?yàn)檫B接中執(zhí)行的事務(wù)少关筒,緩存性能沒有表現(xiàn)出來
- 為主線程使用專用的連接
- 在主線程的專用連接上不執(zhí)行任何讀寫事務(wù)(ReadWrite transaction),只執(zhí)行只讀事務(wù)
- 為讀寫操作建立單獨(dú)的連接
相關(guān) API 與操作
1. 刪除
-
刪除指定集合的所有元素
- (void)removeAllObjectsInCollection:(NSString *)collection
-
刪除數(shù)據(jù)庫所有元素
- (void)removeAllObjectsInAllCollections;
-
刪除某一個(gè)指定元素
- (void)removeObjectForKey:(NSString *)key inCollection:(nullable NSString *)collection;
-
刪除某一些指定元素
- (void)removeObjectsForKeys:(NSArray<NSString *> *)keys inCollection:(nullable NSString *)collection;
2. 獲取總數(shù)
-
獲取集合總數(shù)
- (NSUInteger)numberOfCollections;
-
獲取集合中 key 總數(shù)
- (NSUInteger)numberOfKeysInCollection:(nullable NSString *)collection;
-
獲取數(shù)據(jù)庫中 key 總數(shù)
- (NSUInteger)numberOfKeysInAllCollections;
3. 獲取列表
-
獲取所有集合的列表
- (NSArray<NSString *> *)allCollections;
-
獲取某個(gè)集合中所有 key 的列表
- (NSArray<NSString *> *)allKeysInCollection:(nullable NSString *)collection;
4. 獲取對(duì)象與元數(shù)據(jù)
-
獲取指定對(duì)象
- (nullable id)objectForKey:(NSString *)key inCollection:(nullable NSString *)collection;
-
檢測指定對(duì)象是否在集合中
- (BOOL)hasObjectForKey:(NSString *)key inCollection:(nullable NSString *)collection;
-
獲取指定對(duì)象和元數(shù)據(jù)到指定地址杯缺,存在該 key 則返回 YES平委,否則返回 NO
- (BOOL)getObject:(__nullable id * __nullable)objectPtr metadata:(__nullable id * __nullable)metadataPtr forKey:(NSString *)key inCollection:(nullable NSString *)collection;
-
獲取指定元數(shù)據(jù)
- (nullable id)metadataForKey:(NSString *)key inCollection:(nullable NSString *)collection;
5. 獲取原始數(shù)據(jù)
下面的方法會(huì)跳過 oc 層緩存,直接獲取數(shù)據(jù)庫中的原始數(shù)據(jù)夺谁,因此速度不如上面的方法廉赔。
-
獲取指定對(duì)象
- (nullable NSData *)serializedObjectForKey:(NSString *)key inCollection:(nullable NSString *)collection;
-
獲取指定元數(shù)據(jù)
- (nullable NSData *)serializedMetadataForKey:(NSString *)key inCollection:(nullable NSString *)collection;
讀取指定對(duì)象和元數(shù)據(jù)到指定地址
- (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. 存儲(chǔ)和更新
-
存儲(chǔ)
- (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;
這里要注意如果存儲(chǔ)的對(duì)象所屬 key 是已經(jīng)存儲(chǔ)在數(shù)據(jù)庫的肉微,則會(huì)自動(dòng)更新這個(gè)元素,如果傳遞的對(duì)象是 nil蜡塌,則會(huì)移除這個(gè)元素碉纳。