前言
WCDB是微信移動端團隊開源的移動端數(shù)據(jù)庫組件邓深,提供了一個高效杜窄、完整侄旬、易用的移動端存儲方案望抽。第一次應用到WCDB還是在現(xiàn)公司的工程中加矛,由于現(xiàn)在的team成員主要來自鵝廠,在工程中應用到前東家的東西也是理所當然煤篙,這同時也充分說明了WCDB的易用性斟览,不好用誰會繼續(xù)再使用它呢?本文主要是對WCDB做簡單的介紹以及使用方法的歸納總結(jié)辑奈。
為什么選擇WCDB
- 之所以選擇WCDB苛茂,主要還是因為它的高效已烤、完整、易用性妓羊。
-
高效
WCDB支持多線程的讀讀胯究、讀寫并發(fā)以及寫寫串行執(zhí)行,在批量寫操作的性能測試中躁绸,WCDB性能是FMDB的180%左右裕循。
而在多線程讀寫操作中,WCDB的多線程讀寫操作性能優(yōu)于FMDB 62% 涨颜,而多線程讀操作基本與FMDB持平费韭。FMDB在多線程寫測試中茧球,直接返回錯誤SQLITE_BUSY庭瑰,因此無法比較。而基于SQLite的機制抢埋,WCDB的多線程寫操作實質(zhì)也是串行執(zhí)行弹灭,但不會出錯導致操作中斷。
- 完整
加密:WCDB提供基于SQLCipher的數(shù)據(jù)庫加密揪垄。
損壞修復:WCDB內(nèi)建了Repair Kit用于修復損壞的數(shù)據(jù)庫穷吮。
WCDB提供接口直接獲取SQL的執(zhí)行耗時,可用于監(jiān)控性能饥努。
反注入:WCDB內(nèi)建了對SQL注入的保護 - 易用
WCDB的查詢語言是用WINQ進行查詢捡鱼,無需為了拼接SQL字符串寫很長的代碼。
WCDB的對象關系映射也非常方便酷愧,可以很便捷地定義表驾诈、索引、約束和增刪改查操作等溶浴。
WCDB的使用
- 創(chuàng)建數(shù)據(jù)庫
-(WCTDatabase *)db {
if (_db) {
return _db;
}
//獲取沙盒根目錄
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 文件路徑
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"RSDBService.sqlite"];
NSLog(@"path = %@",filePath);
_db = [[WCTDatabase alloc]initWithPath:filePath];
if (![_db canOpen]) {
NSLog(@"RSDBService.sqlite canOpen fail");
[_db createTableAndIndexesOfName:@"" withClass:[NSArray class]];
}
return _db;
}
- 對象關系映射
WCDB使用內(nèi)建的宏實現(xiàn)ORM的功能乍迄,通過ORM可以達到直接通過Object進行數(shù)據(jù)庫操作。此處需要注意的一點是士败,由于WCDB是基于Objective C++闯两,如果在model的頭文件中引入了<WCDB/WCDB.h>,就需要把.m文件改變?yōu)?mm文件谅将。為了不影響到使用model的controller或者view類漾狼,此處可以用category特性將wcdb的引用隔離。在category中引用<WCDB/WCDB.h>饥臂,并遵守WCTTableCoding協(xié)議邦投,使用WCDB_PROPERTY將聲明綁定到數(shù)據(jù)庫表的字段。以下用一個好友關系的contactModel做舉例說明
首先是category文件擅笔,category中需要引入<WCDB/WCDB.h>并遵守WCTTableCoding協(xié)議
#import "RSContactModel.h"
#import <WCDB/WCDB.h>
@interface RSContactModel (WCTTableCoding) <WCTTableCoding>
WCDB_PROPERTY(uid)
WCDB_PROPERTY(nickName)
WCDB_PROPERTY(avatarUrl)
WCDB_PROPERTY(sex)
WCDB_PROPERTY(delFlag)
WCDB_PROPERTY(addFriendImgUrl)
WCDB_PROPERTY(registerTime)
WCDB_PROPERTY(addFriendTime)
@end
然后是.h文件志衣,在.h中主要做的就是將model所需要暴露的屬性暴露出來屯援,以供其他類使用
#import <UIKit/UIKit.h>
#import "RSModel.h"
#import "Spcgicommdef.pbobjc.h"
@interface RSContactModel : RSModel
@property (nonatomic, strong) NSString *nickName;
@property (nonatomic, assign) long long uid;
@property (nonatomic, strong) NSString *avatarUrl;
@property (nonatomic, assign) RSenSex sex;
@property (nonatomic, assign) RSenDelFlag delFlag;
@property (nonatomic, strong) NSString *addFriendImgUrl;
@property (nonatomic, assign) int32_t registerTime;
@property (nonatomic, assign) int32_t addFriendTime;
@end
最后是.m文件,在.m文件中需要定義類文件中綁定到數(shù)據(jù)庫表的字段以及主鍵的設置念脯、索引的設置以及約束等狞洋。并且在init方法中通過dispacth_once初始化數(shù)據(jù)庫表。
#import "RSContactModel+WCTTableCoding.h"
#import "RSContactModel.h"
#import <WCDB/WCDB.h>
#import "RSDBService.h"
@implementation RSContactModel
WCDB_IMPLEMENTATION(RSContactModel)
WCDB_SYNTHESIZE(RSContactModel, nickName)
WCDB_SYNTHESIZE(RSContactModel, uid)
WCDB_SYNTHESIZE(RSContactModel, avatarUrl)
WCDB_SYNTHESIZE(RSContactModel, sex)
WCDB_SYNTHESIZE(RSContactModel, addFriendImgUrl)
WCDB_SYNTHESIZE_DEFAULT(RSContactModel, delFlag, 0);
WCDB_SYNTHESIZE(RSContactModel, registerTime)
WCDB_SYNTHESIZE(RSContactModel, addFriendTime)
WCDB_UNIQUE(RSContactModel, uid)
WCDB_NOT_NULL(RSContactModel, uid)
-(instancetype)init {
self = [super init];
if (self) {
static dispatch_once_t token;
dispatch_once(&token, ^{
[RSContactModel createDBTable];
});
}
return self;
}
+(void)createDBTable {
if ([[RSDBService db] createTableAndIndexesOfName:NSStringFromClass([RSContactModel class]) withClass:[RSContactModel class]]) {
NSLog(@"creat table RSContactModel success");
} else {
NSLog(@"creat table RSContactModel fail");
}
}
@end
WCDB_PROPERTY用于在頭文件中聲明綁定到數(shù)據(jù)庫表的字段绿店。
WCDB_IMPLEMENTATION吉懊,用于在類文件中定義綁定到數(shù)據(jù)庫表的類。同時假勿,該宏內(nèi)實現(xiàn)了WCTTableCoding借嗽。因此,開發(fā)者無須添加更多的代碼來完成WCTTableCoding的接口
WCDB_SYNTHESIZE转培,用于在類文件中定義綁定到數(shù)據(jù)庫表的字段恶导。
WCDB_PRIMARY用于定義主鍵
WCDB_PRIMARY_AUTO_INCREMENT 用于定義自增主鍵
WCDB_INDEX用于定義索引
WCDB_UNIQUE用于定義唯一約束
WCDB_NOT_NULL用于定義非空約束
- 增刪改查CRUD
對數(shù)據(jù)庫訪問的接口實現(xiàn)建議提供專門的Service類來進行操作,比如對好友關系的數(shù)據(jù)庫模型可以提供一個專門的RSContactService來對RSContact表進行操作浸须。如果熟悉RAC的話惨寿,可以在Service中結(jié)合RAC的信號來通知業(yè)務層說該數(shù)據(jù)庫表有更新。
1.增:
-(BOOL)saveContactList:(NSArray *)contactList {
//contactList中為服務端下發(fā)的contact列表
NSMutableArray *tmp = [[NSMutableArray alloc] init];
for (RSContact *contact in contactList) {
RSContactModel *model = [[RSContactModel alloc] init];
model.uid = contact.uin;
model.nickName = contact.nickName;
model.avatarUrl = contact.headImgURL;
model.sex = (RSenSex)contact.sex;
model.delFlag = (RSenDelFlag)contact.delFlag;
model.addFriendImgUrl = contact.addFriendImgURL;
model.registerTime = contact.registerTime;
model.addFriendTime = contact.addFriendTime;
[tmp addObject:model];
}
BOOL result = [[RSDBService db] insertOrReplaceObjects:tmp into:NSStringFromClass([RSContactModel class])];
if (result) {
[self.updateSignal sendNext:@(YES)];
//如果result為true表示插入數(shù)據(jù)庫成功删窒,發(fā)送一個數(shù)據(jù)庫更新的信號
}
return result;
}
2.刪:
刪除RSContactModel表中uid字段值為testUid的記錄
BOOL result = [[RSDBService db] deleteObjectsFromTable:NSStringFromClass([RSContactModel class]) where: RSContactModel.uid.is(testUid)];
3.改:
以下事例為刪除某個uid為uid值的好友關系的時候裂垦,將contactModel中的delFlag更新為已刪除的代碼。實際上就是更新該記錄中的delFlag的字段
- (BOOL)deleteContactWithUid:(long long)uid {
RSContactModel *contactModel = [[RSContactModel alloc] init];
contactModel.delFlag = RSenDelFlag_DelflagNotExist;
BOOL result = [[RSDBService db] updateRowsInTable:NSStringFromClass([RSContactModel class]) onProperty:RSContactModel.delFlag withObject:contactModel where:RSContactModel.uid.is(uid)];
if (result) {
[self.updateSignal sendNext:@(YES)];
//如果result為true表示修改數(shù)據(jù)庫成功肌索,發(fā)送一個數(shù)據(jù)庫更新的信號
}
return result;
}
4.查:
數(shù)據(jù)庫查詢的接口就更多了蕉拢,這里舉例為根據(jù)uid的array查詢表中uid為array中的值的記錄。
-(NSArray<RSContactModel *>*)getContactsByUids:(NSArray *)uids {
NSArray *tmp = [[RSDBService db] getObjectsOfClass:[RSContactModel class] fromTable:NSStringFromClass([RSContactModel class]) where:RSContactModel.uid.in(uids)];
return tmp;
}
- 事務transaction
WCDB的事務有兩種寫法诚亚,一種是通過block來實現(xiàn)晕换,另一種是通過獲取WCTTransaction來實現(xiàn),block的方式使用更簡單亡电,但是WCTTransaction的方式更易于傳遞佩抹。
//Block的方式
BOOL commited = [[RSDBService db] runTransaction:^BOOL{
[[RSDBService db] insertObject:contact into:NSStringFromClass([RSContactModel class])];
return YES;
}];
//WCTTransaction方式
WCTTransaction *transaction = [[RSDBService db] getTransaction];
BOOL result = [transaction begin];
[[RSDBService db] insertObject:contact into:NSStringFromClass([RSContactModel class])];
result = [transaction commit];
if(!result) {
NSLog(@"%@",[transaction getError]);
}