010-YapDatabase 食用指南

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è)元素碉纳。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市馏艾,隨后出現(xiàn)的幾起案子劳曹,更是在濱河造成了極大的恐慌,老刑警劉巖琅摩,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铁孵,死亡現(xiàn)場離奇詭異,居然都是意外死亡房资,警方通過查閱死者的電腦和手機(jī)蜕劝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轰异,“玉大人岖沛,你說我怎么就攤上這事〈疃溃” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵虫溜,是天一觀的道長容为。 經(jīng)常有香客問我,道長陨献,這世上最難降的妖魔是什么急膀? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布链峭。 她就那樣靜靜地躺著,像睡著了一般杖刷。 火紅的嫁衣襯著肌膚如雪励饵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天滑燃,我揣著相機(jī)與錄音役听,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛典予,可吹牛的內(nèi)容都是我干的甜滨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瘤袖,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼艳吠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起孽椰,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤昭娩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后黍匾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栏渺,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年锐涯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磕诊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纹腌,死狀恐怖霎终,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情升薯,我是刑警寧澤莱褒,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站涎劈,受9級(jí)特大地震影響广凸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛛枚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一谅海、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蹦浦,春花似錦扭吁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至徒河,卻和暖如春系馆,著一層夾襖步出監(jiān)牢的瞬間送漠,已是汗流浹背顽照。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人代兵。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓尼酿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親植影。 傳聞我的和親對(duì)象是個(gè)殘疾皇子裳擎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容