@TOC
IOS數(shù)據(jù)存儲簡介
- 在項目開發(fā)當(dāng)中,我們經(jīng)常會對一些數(shù)據(jù)進行本地緩存處理岛都。離線緩存的數(shù)據(jù)一般都保存在APP所在的沙盒之中淑履。一般有以下幾種:
存儲方式 | 優(yōu)點 | 缺點 | 適用場景 | 備注 |
---|---|---|---|---|
PList(XML屬性列表) | 快速效率高 | 不靈活钱贯,只能存儲常用的類型 | 只適用于系統(tǒng)自帶的一些常用類型才能用 | 備注 |
偏好設(shè)置(NSUserDefaults) | 快速效率高窗宇,簡單易用 | 只能存儲常用的類型 | 適用場景 | 備注 |
歸檔(NSCoding NSKeyedArchiver NSKeyedUnarchiver) | 歸檔可以實現(xiàn)把自定義的對象存放在文件中 | 必須實現(xiàn)協(xié)議措伐,浸入性強 | 適用場景 | 備注 |
SQLITE數(shù)據(jù)庫 | 靈活 | 操作復(fù)雜 | 適用場景 | 備注 |
FMDB | 是對sqlite的封裝,簡單易用军俊,接口比原生的SQLite接口簡潔很多侥加,提供一些多線程,緩存粪躬,線程池的功能 | 缺點 | 適用場景 | 備注 |
CoreData | 蘋果自帶的數(shù)據(jù)存儲方式 | 缺點 | 適用場景 | 備注 |
WCDB | 騰訊開發(fā)的一款DB工具 | 缺點 | 適用場景 | 備注 |
IOS 沙盒存儲路徑
- 要存儲數(shù)據(jù)担败,必須要了解蘋果的沙盒存儲機制,沙盒相關(guān)路徑的規(guī)則
沙盒目錄 | 路徑說明 | 備注 |
---|---|---|
Documents | iTunes會備份該目錄镰官。一般用來存儲需要持久化的數(shù)據(jù)提前。 | |
Library/Caches | 緩存,iTunes不會備份該目錄泳唠。內(nèi)存不足時會被清除狈网,應(yīng)用沒有運行時,可能會被清除笨腥,拓哺。一般存儲體積大、不需要備份的非重要數(shù)據(jù)脖母。 | |
Library/Preference | iTunes同會備份該目錄士鸥,可以用來存儲一些偏好設(shè)置。 | |
tmp | iTunes不會備份這個目錄谆级,用來保存臨時數(shù)據(jù)烤礁,應(yīng)用退出時會清除該目錄下的數(shù)據(jù)。 |
- 獲取沙盒路徑
// 獲取Documents目錄的路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
// 得到Document目錄下的fileName文件的路徑
NSString *filePath = [documentPath stringByAppendingPathComponent:fileName];
//獲取Library/Caches目錄路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
//獲取Library/Caches目錄下的fileName文件路徑
NSString *filePath = [path stringByAppendingPathComponent:fileName];
//獲取temp路徑
NSString *tmp = NSTemporaryDirectory();
//獲取temp下fileName文件的路徑
NSString *filePath = [tmp stringByAppendingPathComponent:fileName];
IOS數(shù)據(jù)存儲方式
1. PList(XML屬性列表)
- 在使用plist進行數(shù)據(jù)存儲和讀取肥照,只適用于系統(tǒng)自帶的一些常用類型才能用脚仔,且必須先獲取路徑相對麻煩
- 可以把字典或數(shù)組直接寫入到文件中。另外舆绎,NSString玻侥、NSData、NSNumber等類型亿蒸,也可以使用writeToFile:atomically:方法直接將對象寫入文件中凑兰,只是Type為空。
- 實例1
oc代碼
//寫入文件
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [doc stringByAppendingPathComponent:@"myself.plist"];
NSDictionary *dict = @{@"name": @"yixiang"};
[dict writeToFile:path atomically:YES];
//讀取文件
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
2. 偏好設(shè)置(NSUserDefaults)
- 將所有的東西都保存在同一個文件夾下面边锁,且主要用于存儲應(yīng)用的設(shè)置信息)
- NSUserDefaults可以存儲的數(shù)據(jù)類型包括:NSData姑食、NSString、NSNumber茅坛、NSDate音半、NSArray则拷、NSDictionary。如果要存儲其他類型曹鸠,則需要轉(zhuǎn)換為前面的類型煌茬,才能用NSUserDefaults存儲。
- 實例2
OC代碼
//寫入文件
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:@"yixiang" forKey:@"name"];
[defaults setInteger:27 forKey:@"age"];
[defaults synchronize];
//讀取文件
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSString *name=[defaults objectForKey:@"name"];
NSInteger age=[defaults integerForKey:@"age"];
3. 歸檔(NSCoding NSKeyedArchiver NSKeyedUnarchiver)
- 因為
PList
存儲和NSUserDefaults
存儲都有一個致命的缺陷彻桃,只能存儲常用的類型坛善。歸檔可以實現(xiàn)把自定義的對象存放在文件中。 - 需要保存的對象必須遵守
NSCoding
協(xié)議邻眷,并且實現(xiàn)該協(xié)議中- (void)encodeWithCoder:(NSCoder )aCoder和 - (id)initWithCoder:(NSCoder )aDecoder方法
眠屎。 - 實例3
OC代碼
//YXPerson.h文件如下:
@interface YXPerson : NSObject<NSCoding>
@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) int age;
@end
//YXPerson.m文件如下:
#import "YYPerson.h"
@implementation YYPerson
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self=[super init]) {
self.name=[aDecoder decodeObjectForKey:@"name"];
self.age=[aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
@end
在ViewController中對它進行寫入和讀取
//寫入對象
YXPerson *p=[[YXPerson alloc]init];
p.name=@"yixiang";
p.age=27;
NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *path=[docPath stringByAppendingPathComponent:@"person.yixiang"];
[NSKeyedArchiver archiveRootObject:p toFile:path];
//讀取對象
YXPerson *p=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
4. SQLITE數(shù)據(jù)庫
上述三種方法都無法存儲大批量的數(shù)據(jù),有性能的問題肆饶。
SQLITE作為程序員都比較熟悉改衩,它是跨平臺的數(shù)據(jù)庫工具。下面簡單介紹一下驯镊,如何打開數(shù)據(jù)庫葫督,新增一張表格,然后對其進行增刪改查的操作板惑。
詳細(xì)SQLite相關(guān)性能優(yōu)化可以參考這篇文章:微信iOS SQLite源碼優(yōu)化實踐
實例4
OC代碼
- (void)openDB{
//獲取數(shù)據(jù)庫文件路徑
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"];
//將OC字符串轉(zhuǎn)換為c語言的字符串
const char *cfileName = fileName.UTF8String;
//打開數(shù)據(jù)庫文件(如果數(shù)據(jù)庫文件不存在候衍,那么該函數(shù)會自動創(chuàng)建數(shù)據(jù)庫文件)
int result = sqlite3_open(cfileName, &_db);
if (result == SQLITE_OK) {//打開成功
NSLog(@"成功打開數(shù)據(jù)庫");
}else{
NSLog(@"打開數(shù)據(jù)庫失敗");
}
}
- (void)createTable{
//創(chuàng)建表
const char *sql = "CREATE TABLE IF NOT EXISTS t_student(id integer PRIMARY KEY AUTOINCREMENT,name text NOT NULL,age integer NOT NULL);";
char *errmsg= NULL;
int result = sqlite3_exec(_db, sql, NULL, NULL, &errmsg);
if (result==SQLITE_OK) {
NSLog(@"創(chuàng)建表成功");
}else{
NSLog(@"創(chuàng)建表失敗---%s",errmsg);
}
}
- (void)insertData{
//插入數(shù)據(jù)
for (int i=0; i<10; i++) {
//拼接sql語句
NSString *name = [NSString stringWithFormat:@"yixiangboy--%d",arc4random_uniform(100)];
int age = arc4random_uniform(20)+10;
NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_student (name,age) VALUES ('%@',%d);",name,age];
//執(zhí)行SQL語句
char *errmsg = NULL;
sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {//如果有錯誤信息
NSLog(@"插入數(shù)據(jù)失敗--%s",errmsg);
}else{
NSLog(@"插入數(shù)據(jù)成功");
}
}
}
- (void)deleteData{
//刪除age小于15的數(shù)據(jù)
NSString *sql = [NSString stringWithFormat:@"DELETE FROM t_student WHERE age<15"];
char *errmsg = NULL;
sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {
NSLog(@"刪除數(shù)據(jù)失敗");
}else{
NSLog(@"刪除數(shù)據(jù)成功");
}
}
- (void)updateData{
//大于20歲的都置為20歲
NSString *sql = [NSString stringWithFormat:@"UPDATE t_student set age=20 WHERE age>20"];
char *errmsg = NULL;
sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {
NSLog(@"更新數(shù)據(jù)失敗");
}else{
NSLog(@"更新數(shù)據(jù)成功");
}
}
- (void)queryData{
const char *sql = "SELECT id,name,age FROM t_student WHERE age<20";
sqlite3_stmt *stmt = NULL;
//進行查詢前的準(zhǔn)備工作
if(sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL)==SQLITE_OK){//SQL語句沒有問題
NSLog(@"查詢語句沒有問題");
//每調(diào)用一次sqlite3_step函數(shù),stmt就會指向下一條記錄
while (sqlite3_step(stmt)==SQLITE_ROW) {//找到一條記錄
//取出數(shù)據(jù)
//(1)取出第0個字段的值(int)
int ID=sqlite3_column_int(stmt, 0);
//(2)取出第一列字段的值(text)
const unsigned char *name = sqlite3_column_text(stmt, 1);
//(3)取出第二列字段的值(int)
int age = sqlite3_column_int(stmt, 2);
printf("%d %s %d\n",ID,name,age);
}
}else{
NSLog(@"查詢語句有問題");
}
}
5. FMDB
5.1 FMDB 簡介
- FMDB 做IOS的也應(yīng)該都耳熟能詳了洒放,項目中都用過。是一個很牛逼的DB工具滨砍。
- FMDB 是對SQLIite數(shù)據(jù)庫的C語言接口進行了一層封裝往湿,使其滿足面向?qū)ο蟮牟僮鳎涌诒仍腟QLite接口簡潔很多惋戏。同時也提供一些多線程领追,緩存,線程池的功能响逢。
- FMDB的三個類:
類 | 功能 | 備注 |
---|---|---|
FMDatabase | 可以理解成一個數(shù)據(jù)庫 | 備注 |
FMResultSet | 查詢的結(jié)果集合 | 備注 |
FMDatabaseQueue | 運用多線程绒窑,可執(zhí)行多個查詢、更新舔亭,線程安全 | 備注 |
5.2 FMDB 創(chuàng)建數(shù)據(jù)庫
- FMDatabase創(chuàng)建的時候需要提供一個SQLite數(shù)據(jù)庫文件些膨。我們一般提供一個.db的文件路徑即可。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
5.3 FMDB 打開數(shù)據(jù)庫钦铺,關(guān)閉數(shù)據(jù)庫
- 打開數(shù)據(jù)庫:在和數(shù)據(jù)庫進行交互之前订雾,我們需要先打開數(shù)據(jù)庫。使用上一步拿到的數(shù)據(jù)庫文件操作句柄db
if (![db open])
{
db = nil;
return;
}
- 關(guān)閉數(shù)據(jù)庫
[db close];
5.4 FMDB 創(chuàng)建表
- 使用集合語句來進行表的創(chuàng)建矛洞。
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
"create table bulktest2 (id integer primary key autoincrement, y text);"
"create table bulktest3 (id integer primary key autoincrement, z text);"
"insert into bulktest1 (x) values ('XXX');"
"insert into bulktest2 (y) values ('YYY');"
"insert into bulktest3 (z) values ('ZZZ');";
success = [db executeStatements:sql];
5.5 FMDB 增洼哎,刪,改,查
5.5.1 查詢
- 查詢:使用executeQuery 來執(zhí)行查詢語句噩峦,F(xiàn)MResultSet來進行存儲查詢的結(jié)果锭沟。
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
//retrieve values for each record
}
//可通過如下的方式將結(jié)果集里面的數(shù)據(jù)取出來。
while ([rs next])
{
if (oldIDs == nil) oldIDs = [[NSMutableArray alloc] init];
[oldIDs addObject:[rs stringForColumnIndex:0]];
}
另外识补,F(xiàn)MResultSet可以通過如下的方法將數(shù)據(jù)通過恰當(dāng)?shù)母袷綑z索出來族淮。
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumn:
objectForColumn:
5.5.2 更新
- 通過調(diào)用
- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments
執(zhí)行插入、刪除或者更新數(shù)據(jù)李请。
插入數(shù)據(jù)或者更新數(shù)據(jù)瞧筛。
NSString *sql = ![rs next] ? INSERT_STATEMENT : UPDATE_STATEMENT;
BOOL success = [db executeUpdate:sql withParameterDictionary:[self pr_toDBDictionary]];
if (!success)
{
STLogDBLastError(db);
result = NO;
return;
}
5.5.3 刪除
- 通過執(zhí)行sql語句刪除
BOOL success = [db executeUpdate:@"DELETE FROM STAppLog WHERE userID = ? AND appLogID = ?;", self.userID, self.appLogID];
if (!success)
{
STLogDBLastError(db);
result = NO;
return;
}
5.5.4 新增
5.6 FMDB 多線程操作數(shù)據(jù)庫
- FMDatabaseQueue內(nèi)部實現(xiàn)了FMDatabase相關(guān)的創(chuàng)建及管理。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while ([rs next]) {
…
}
}];
- 執(zhí)行事務(wù)
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
if (whoopsSomethingWrongHappened) {
*rollback = YES;
return;
}
// etc ...
}];
更多事務(wù)相關(guān)知識參考這篇博客:數(shù)據(jù)庫事務(wù) ios FMDB
5.7 FMDatabase源碼解析
- (BOOL)openWithFlags:(int)flags vfs:(NSString *)vfsName {
#if SQLITE_VERSION_NUMBER >= 3005000
if (_db) {
return YES;
}
int err = sqlite3_open_v2([self sqlitePath], (sqlite3**)&_db, flags, [vfsName UTF8String]);
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
}
if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}
return YES;
#else
NSLog(@"openWithFlags requires SQLite 3.5");
return NO;
#endif
}
- vfs是虛擬文件系統(tǒng)的簡稱导盅,主要是用來統(tǒng)一不同平臺操作系統(tǒng)文件的訪問较幌,屏蔽底層硬件介質(zhì),提供統(tǒng)一的訪問接口白翻。
- 我們可以看到通過 sqlite3_open_v2這個函數(shù)乍炉,在指定的path上面打開了數(shù)據(jù)庫的句柄:_db.
- 后面,我們就可以拿這個句柄去訪問數(shù)據(jù)庫了滤馍,比如創(chuàng)建庫岛琼、創(chuàng)建表、插入數(shù)據(jù)巢株、更新數(shù)據(jù)槐瑞、查詢數(shù)據(jù)等。
5.8 FMResultSet源碼解析
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
// 先判斷這個句柄是否存在阁苞,即操作數(shù)據(jù)庫的入口
if (![self databaseExists]) {
return 0x00;
}
// 是否正在執(zhí)行查詢
if (_isExecutingStatement) {
[self warnInUse];
return 0x00;
}
_isExecutingStatement = YES;
int rc = 0x00;
//定義一個stmt存放結(jié)果集
sqlite3_stmt *pStmt = 0x00;
// 主要是做 銷毀stmt的工作困檩,防止內(nèi)存泄漏
FMStatement *statement = 0x00;
FMResultSet *rs = 0x00;
if (_traceExecution && sql) {
NSLog(@"%@ executeQuery: %@", self, sql);
}
if (_shouldCacheStatements) {
statement = [self cachedStatementForQuery:sql];
pStmt = statement ? [statement statement] : 0x00;
[statement reset];
}
if (!pStmt) {
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
if (SQLITE_OK != rc) {
if (_logsErrors) {
NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(@"DB Query: %@", sql);
NSLog(@"DB Path: %@", _databasePath);
}
if (_crashOnErrors) {
NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
abort();
}
sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return nil;
}
}
id obj;
int idx = 0;
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
// If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
if (dictionaryArgs) {
for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
// Prefix the key with a colon.
NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
if (_traceExecution) {
NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
}
// Get the index for the parameter name.
int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
FMDBRelease(parameterName);
if (namedIdx > 0) {
// Standard binding from here.
[self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
// increment the binding count, so our check below works out
idx++;
}
else {
NSLog(@"Could not find index for %@", dictionaryKey);
}
}
}
else {
while (idx < queryCount) {
if (arrayArgs && idx < (int)[arrayArgs count]) {
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
}
else if (args) {
obj = va_arg(args, id);
}
else {
//We ran out of arguments
break;
}
if (_traceExecution) {
if ([obj isKindOfClass:[NSData class]]) {
NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
}
else {
NSLog(@"obj: %@", obj);
}
}
idx++;
[self bindObject:obj toColumn:idx inStatement:pStmt];
}
}
if (idx != queryCount) {
NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return nil;
}
FMDBRetain(statement); // to balance the release below
if (!statement) {
statement = [[FMStatement alloc] init];
[statement setStatement:pStmt];
if (_shouldCacheStatements && sql) {
[self setCachedStatement:statement forQuery:sql];
}
}
// the statement gets closed in rs's dealloc or [rs close];
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
[rs setQuery:sql];
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[_openResultSets addObject:openResultSet];
[statement setUseCount:[statement useCount] + 1];
FMDBRelease(statement);
_isExecutingStatement = NO;
return rs;
}
5.9 FMDatabaseQueue源碼解析
- (void)inDatabase:(void (^)(FMDatabase *db))block {
#ifndef NDEBUG
/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
* and then check it against self to make sure we're not about to deadlock. */
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
#endif
FMDBRetain(self);
dispatch_sync(_queue, ^() {
FMDatabase *db = [self database];
block(db);
if ([db hasOpenResultSets]) {
NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
#if defined(DEBUG) && DEBUG
NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
NSLog(@"query: '%@'", [rs query]);
}
#endif
}
});
FMDBRelease(self);
}
- FMDatabaseQueue 使用實例
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 1、查詢所有的日志那槽。
NSArray *arrLog = [self dd_fetchAppLogs];
});
+ (NSArray *)dd_fetchAppLogs
{
__block NSMutableArray *appLogs = nil;
WSELF;
[[FMDatabaseQueue sharedInstance] inDatabase:^(FMDatabase *db) {
SSELF;
FMResultSet *rs = [db executeQuery:@"SELECT * FROM STAppLog;"];
@onExit {
[rs close];
};
if (rs == nil)
{
STLogDBLastError(db);
return;
}
while ([rs next])
{
@autoreleasepool {
if (appLogs == nil) appLogs = [[NSMutableArray alloc] init];
STAppLog *appLog = [self mj_objectWithKeyValues:rs.resultDictionary];
[appLogs addObject:appLog];
}
}
}];
return appLogs;
}
6. CoreData
6.1 創(chuàng)建數(shù)據(jù)庫表
- 在MesaSQLite設(shè)計器中創(chuàng)建表結(jié)構(gòu)悼沿,然后將生成的sql復(fù)制出來使用。這樣可以避免手敲代碼產(chǎn)生的錯誤骚灸。
- 將sql保存成文件糟趾,然后放到xcode工程中。
6.2 數(shù)據(jù)模型管理類NSManagedObjectModel
通過NSManagedObjectModel甚牲,可以將創(chuàng)建的數(shù)據(jù)模型文件讀取為模型管理類對象义郑。實體類似于數(shù)據(jù)庫中的表結(jié)構(gòu)。
創(chuàng)建NSManagedObjectModel對象
首先丈钙、我們需要知道數(shù)據(jù)模型文件在哪魔慷,通過數(shù)據(jù)模型文件加載NSManagedObjectModel。
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [DDBITrackKitBUNDLE URLForResource:@"Model" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
6.3 持久化存儲協(xié)調(diào)者類NSPersistentStoreCoordinator
- NSManagedObjectContext是進行數(shù)據(jù)管理的核心類著恩,我們通過這個類來進行數(shù)據(jù)的增刪改查等操作院尔。
- PersistentStoreCoordinator對象的創(chuàng)建需要用到ManagedObjectModel對象蜻展,根據(jù)objectModel的模型結(jié)構(gòu)創(chuàng)建持久化的本地存儲。同時PersistentStoreCoordinator對象需要知道數(shù)據(jù)庫文件在哪里邀摆,以便打開對一個的數(shù)據(jù)庫纵顾。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// 指定一個數(shù)據(jù)庫文件路徑
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DDBITrackKit.sqlite"];
NSError *error = nil;
// 通過ManagedObjectModel對象創(chuàng)建NSPersistentStoreCoordinator對象。
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// 指定底層的存儲方式為SQLite數(shù)據(jù)庫栋盹。
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
//
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
6.4 數(shù)據(jù)對象管理上下文NSManagedObjectContext
- 創(chuàng)建NSManagedObjectContext對象
操作數(shù)據(jù)需要用到Object的句柄施逾,每個句柄管理了對應(yīng)的ObjectModel對象。一般我們會創(chuàng)建一個主句柄例获。然后使用子句柄執(zhí)行多線程操作汉额。
需要注意的點是:各個線程創(chuàng)建的子句柄相互之間是不能直接進行通信的,后果是有可能會崩潰榨汤,以及查詢不出數(shù)據(jù)蠕搜、或者查詢出來錯誤的數(shù)據(jù)。如果需要通信收壕,那么需要把實體(Entity)的ObjectID傳遞給另一個線程妓灌,然后這個線程里的context執(zhí)行對應(yīng)的查詢,然后再改變查詢出來的object的數(shù)據(jù)蜜宪。比較繁瑣虫埂。
保存數(shù)據(jù)的時候∑匝椋可以調(diào)用子句柄的save:函數(shù)掉伏,然后會在我們的通知中心里拿到這個數(shù)據(jù)改變的通知。
- 創(chuàng)建主句柄
- (NSManagedObjectContext *)mainManagedObjectContext
{
if (_mainManagedObjectContext != nil) {
return _mainManagedObjectContext;
}
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainManagedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
return _mainManagedObjectContext;
}
- 創(chuàng)建多線程的子句柄
- (NSManagedObjectContext *)privateContext
{
// 設(shè)置一個 persistent store coordinator 和兩個獨立的 contexts 被證明了是在后臺處理 Core Data 的好方法澳窑。
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = [self persistentStoreCoordinator];
return context;
}
- 創(chuàng)建數(shù)據(jù)更改的通知
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
NSManagedObjectContext *moc = self.mainManagedObjectContext;
if (note.object != moc) {
[moc performBlock:^{
[moc mergeChangesFromContextDidSaveNotification:note];
}];
}
}];
6.5 查詢數(shù)據(jù)
- 通過NSEntityDescription來查詢所需要的實體對象斧散。
+ (instancetype)findOrCreateWithIdentifier:(id)identifier inContext:(NSManagedObjectContext *)context
{
id object = nil;
NSString *entityName = NSStringFromClass(self);
if (identifier)
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"appLogID = %@", identifier];
fetchRequest.fetchLimit = 1;
fetchRequest.returnsObjectsAsFaults = NO;
NSError *error = nil;
id object = [[context executeFetchRequest:fetchRequest error:&error] lastObject];
if (object == nil) {
object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
}
}
else
{
object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
}
return object;
}
+ (NSArray *)dd_fetchAllAppLog
{
NSManagedObjectContext *context = [DatabaseHelper shareInstance].store.privateContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"DDBILog"];
[request setReturnsObjectsAsFaults:NO];
NSError *error;
NSArray *arrResult = [context executeFetchRequest:request error:&error];
return arrResult;
}
6.6 更新數(shù)據(jù)
- 先查詢出來對象,然后更新對象響應(yīng)的字段照捡,最后調(diào)用save函數(shù)進行保存
- (void)dd_saveOrUpdateWithContext:(NSManagedObjectContext *)localContext completion:(DatabaseCompletion)completion
{
DDBILog *tAppLog = [DDBILog findOrCreateWithIdentifier:self.appLogID inContext:localContext];
tAppLog.accuracy = self.accuracy;
[localContext save:NULL];
if (completion) {
completion(YES, nil);
}
}
6.7 刪除對象
- (void)dd_delete:(DatabaseCompletion)completion
{
NSManagedObjectContext *localContext = [DatabaseHelper shareInstance].store.privateContext;
[self dd_deleteWithContext:localContext completion:completion];
}
- (void)dd_deleteWithContext:(NSManagedObjectContext *)context completion:(DatabaseCompletion)completion
{
[self MR_deleteEntityInContext:context];
[context save:NULL];
}