如何存儲(chǔ)數(shù)據(jù)
- 1掘殴、文件
- 3、NSUserDefaults
- 2爹谭、數(shù)據(jù)庫
文件
- 1、沙盒
- 2每聪、Plist
- 3旦棉、NSKeyedArchiver歸檔 / NSKeyedUnarchiver解檔
NSUserDefaults
數(shù)據(jù)庫
- 1、SQLite3
- 2药薯、FMDB
- 3、Core Data
文件
沙盒
iOS本地化存儲(chǔ)的數(shù)據(jù)保存在沙盒中救斑。
- Documents:iTunes會(huì)備份該目錄童本。一般用來存儲(chǔ)需要持久化的數(shù)據(jù)。
- Library/Caches:緩存脸候,iTunes不會(huì)備份該目錄穷娱。內(nèi)存不足時(shí)會(huì)被清除,應(yīng)用沒有運(yùn)行時(shí)运沦,可能會(huì)被清除泵额,。一般存儲(chǔ)體積大携添、不需要備份的非重要數(shù)據(jù)嫁盲。
- Library/Preference:iTunes同會(huì)備份該目錄,可以用來存儲(chǔ)一些偏好設(shè)置烈掠。
- tmp: iTunes不會(huì)備份這個(gè)目錄羞秤,用來保存臨時(shí)數(shù)據(jù),應(yīng)用退出時(shí)會(huì)清除該目錄下的數(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];
其中:
- NSDocumentDirectory: 第一個(gè)參數(shù)代表要查找哪個(gè)文件瘾蛋,是一個(gè)枚舉。
- NSUserDomainMask: 也是一個(gè)枚舉矫限,表示搜索的范圍限制于當(dāng)前應(yīng)用的沙盒目錄哺哼。。
- YES (expandTilde): 第三個(gè)參數(shù)是一個(gè)BOOL值叼风。iOS中主目錄的全寫形式是/User/userName取董,這個(gè)參數(shù)填YES就表示全寫,填NO就是寫成‘‘~’’咬扇。
plist
可以把字典或數(shù)組直接寫入到文件中甲葬。另外,NSString懈贺、NSData经窖、NSNumber等類型坡垫,也可以使用writeToFile:atomically:方法直接將對(duì)象寫入文件中,只是Type為空画侣。
存儲(chǔ)
// 要保存的數(shù)據(jù)
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"iOS cookbook" forKey:@"bookName"];
// 獲取路徑.
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
// 寫入數(shù)據(jù)
[dict writeToFile:filePath atomically:YES];
讀取
// 文件路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
// 解析數(shù)據(jù)
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *result = dict[@"bookName"];
NSLog(@"%@", result);
NSKeyedArchiver歸檔 / NSKeyedUnarchiver解檔
NSKeyedArchiver的三個(gè)使用場景:
- 1.對(duì)單個(gè)簡單對(duì)象進(jìn)行歸檔/解檔冰悠。
// 獲取歸檔文件路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"knight"];
// 對(duì)字符串@”test”進(jìn)行歸檔,寫入到filePath中
[NSKeyedArchiver archiveRootObject:@"test" toFile:filePath];
// 根據(jù)保存數(shù)據(jù)的路徑filePath解檔數(shù)據(jù)
NSString *result = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
// 打印歸檔的數(shù)據(jù)
NSLog(@"%@",result);
- 2.對(duì)多個(gè)對(duì)象進(jìn)行歸檔/解檔
// 獲取歸檔路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];
// 用來承載數(shù)據(jù)的NSMutableData
NSMutableData *data = [[NSMutableData alloc] init];
// 歸檔對(duì)象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// 將要被保存的三個(gè)數(shù)據(jù)
NSString *name = @"jack";
int age = 18;
double height = 1.80;
// 運(yùn)用encodeObject:方法歸檔數(shù)據(jù)
[archiver encodeObject:name forKey:@"name"];
[archiver encodeInt:age forKey:@"age"];
[archiver encodeDouble:height forKey:@"height"];
// 結(jié)束歸檔
[archiver finishEncoding];
// 寫入數(shù)據(jù)(存儲(chǔ)數(shù)據(jù))
[data writeToFile:filePath atomically:YES];
// NSMutableData用來承載解檔出來的數(shù)據(jù)
NSMutableData *resultData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
// 解檔對(duì)象
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:resultData];
// 分別解檔出三個(gè)數(shù)據(jù)
NSString *resultName = [unArchiver decodeObjectForKey:@"name"];
int resultAge = [unArchiver decodeIntForKey:@"age"];
double resultHeight = [unArchiver decodeDoubleForKey:@"height"];
//結(jié)束解檔
[unArchiver finishDecoding];
// 打印
NSLog(@"name = %@, age = %d, height = %.2f", resultName, resultAge, resultHeight);
- 3.歸檔保存自定義對(duì)象
如果想對(duì)一個(gè)自定義對(duì)象進(jìn)行歸檔解檔配乱,首先要讓對(duì)象遵守NSCoding協(xié)議溉卓。
@interface DDAppConfigModel : NSObject <NSCoding>
@property(nonatomic, copy) NSString *paramName;
@property(nonatomic, copy) NSString *paramValue;
@property(nonatomic, copy) NSString *version;
@property(nonatomic, copy) NSString *delFlag;
@property(nonatomic, copy) NSString *type;
- (instancetype)initWithDict:(NSDictionary *)dict;
- (void)updateWithDict:(NSDictionary *)dict;
@end
其中:NSCoding協(xié)議有2個(gè)方法:
- (void)encodeWithCoder:(NSCoder *)aCoder
歸檔時(shí)調(diào)用這個(gè)方法,在方法中使用encodeObject:forKey:歸檔變量搬泥。
- (instancetype)initWithCoder:(NSCoder *)aDecoder
解檔時(shí)調(diào)用這個(gè)方法桑寨,在方法中使用decodeObject:forKey讀出變量。
@implementation DDAppConfigModel
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init])
{
self.paramName = [aDecoder decodeObjectForKey:@"paramName"];
self.paramValue = [aDecoder decodeObjectForKey:@"paramValue"];
self.version = [aDecoder decodeObjectForKey:@"version"];
self.delFlag = [aDecoder decodeObjectForKey:@"delFlag"];
self.type = [aDecoder decodeObjectForKey:@"type"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.paramName forKey:@"paramName"];
[aCoder encodeObject:self.paramValue forKey:@"paramValue"];
[aCoder encodeObject:self.version forKey:@"version"];
[aCoder encodeObject:self.delFlag forKey:@"delFlag"];
[aCoder encodeObject:self.type forKey:@"type"];
}
//類方法忿檩,運(yùn)用NSKeyedArchiver歸檔數(shù)據(jù)
+ (void)saveConfig:(DDAppConfigModel *)config
{
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *path = [docPath stringByAppendingPathComponent:@"DDAppConfigModel.plist"];
[NSKeyedArchiver archiveRootObject:config toFile:path];
}
//類方法尉尾,使用NSKeyedUnarchiver解檔數(shù)據(jù)
+ (DDAppConfigModel *)getConfig
{
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *path=[docPath stringByAppendingPathComponent:@"DDAppConfigModel.plist"];
DDAppConfigModel *config = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return config;
}
@end
NSUserDefaults
一般使用它來進(jìn)行一些設(shè)置的記錄,比如用戶ID燥透,開關(guān)是否打開等設(shè)置沙咏。通過鍵值對(duì)的方式記錄設(shè)置。
NSUserDefaults可以存儲(chǔ)的數(shù)據(jù)類型包括:NSData班套、NSString肢藐、NSNumber、NSDate吱韭、NSArray吆豹、NSDictionary。如果要存儲(chǔ)其他類型杉女,則需要轉(zhuǎn)換為前面的類型瞻讽,才能用NSUserDefaults存儲(chǔ)。
// 通用型設(shè)置數(shù)據(jù)
#define kSetUserDefaults(key, value) ([USER_DEFAULT setObject:value forKey:key], [USER_DEFAULT synchronize])
// 通用型獲取數(shù)據(jù)
#define kUserDefaults(key) [USER_DEFAULT objectForKey:key]
數(shù)據(jù)庫
SQLite3
- 添加庫文件libsqlite3.0.tbd
- 導(dǎo)入頭文件#import
- 打開數(shù)據(jù)庫
- 創(chuàng)建表
- 增刪改查操作
- 關(guān)閉數(shù)據(jù)庫
FMDB
FMDB封裝了SQLite的C語言API熏挎,更加面向?qū)ο蟆?/p>
FMDB中的三個(gè)類:
- FMDatabase:可以理解成一個(gè)數(shù)據(jù)庫速勇。
- FMResultSet:查詢的結(jié)果集合。
- FMDatabaseQueue:運(yùn)用多線程坎拐,可執(zhí)行多個(gè)查詢烦磁、更新。線程安全哼勇。
創(chuàng)建數(shù)據(jù)庫都伪。
FMDatabase
創(chuàng)建的時(shí)候需要提供一個(gè)SQLite數(shù)據(jù)庫文件。我們一般提供一個(gè).db的文件路徑即可积担。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
打開數(shù)據(jù)庫
在和數(shù)據(jù)庫進(jìn)行交互之前陨晶,我們需要先打開數(shù)據(jù)庫。使用上一步拿到的數(shù)據(jù)庫文件操作句柄db
if (![db open])
{
db = nil;
return;
}
創(chuàng)建表
使用集合語句來進(jìn)行表的創(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];
查詢
使用executeQuery 來執(zhí)行查詢語句先誉,F(xiàn)MResultSet來進(jìn)行存儲(chǔ)查詢的結(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]];
}
另外褐耳,FMResultSet
可以通過如下的方法將數(shù)據(jù)通過恰當(dāng)?shù)母袷綑z索出來诈闺。
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumn:
objectForColumn:
更新
通過調(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;
}
刪除數(shù)據(jù)
BOOL success = [db executeUpdate:@"DELETE FROM STAppLog WHERE userID = ? AND appLogID = ?;", self.userID, self.appLogID];
if (!success)
{
STLogDBLastError(db);
result = NO;
return;
}
多線程操作數(shù)據(jù)庫
FMDatabaseQueue內(nèi)部實(shí)現(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]) {
…
}
}];
事務(wù)
參考:https://blog.csdn.net/x32sky/article/details/45531229
[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 ...
}];
關(guān)閉數(shù)據(jù)庫
[db close];
FMDatabase源碼解析
管理數(shù)據(jù)庫刃滓。需要指定一個(gè)文件用來存儲(chǔ)數(shù)據(jù)仁烹。例如:xxx/xxx/evian.db
然后,調(diào)用如下方法打開數(shù)據(jù)庫:
- (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ái)操作系統(tǒng)文件的訪問晃危,屏蔽底層硬件介質(zhì),提供統(tǒng)一的訪問接口老客。
我們可以看到通過 sqlite3_open_v2
這個(gè)函數(shù),在指定的path上面打開了數(shù)據(jù)庫的句柄:_db
.
后面震叮,我們就可以拿這個(gè)句柄去訪問數(shù)據(jù)庫了胧砰,比如創(chuàng)建庫、創(chuàng)建表苇瓣、插入數(shù)據(jù)尉间、更新數(shù)據(jù)、查詢數(shù)據(jù)等击罪。
FMResultSet源碼解析
下面我們來看看FMDatabase最主要的一個(gè)接口:
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
// 先判斷這個(gè)句柄是否存在哲嘲,即操作數(shù)據(jù)庫的入口
if (![self databaseExists]) {
return 0x00;
}
// 是否正在執(zhí)行查詢
if (_isExecutingStatement) {
[self warnInUse];
return 0x00;
}
_isExecutingStatement = YES;
int rc = 0x00;
//定義一個(gè)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;
}
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);
}
如何使用呢媳禁,請(qǐng)看如下調(diào)用:
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;
}
通過源碼可以知道姜贡,該查詢操作將在FMDB內(nèi)部創(chuàng)建的隊(duì)列中執(zhí)行子線程的查詢操作功舀。
CoreData
創(chuàng)建數(shù)據(jù)庫表
在MesaSQLite設(shè)計(jì)器中創(chuàng)建表結(jié)構(gòu),然后將生成的sql復(fù)制出來使用毫别。這樣可以避免手敲代碼產(chǎn)生的錯(cuò)誤娃弓。
將sql保存成文件,然后放到xcode工程中岛宦。
數(shù)據(jù)模型管理類NSManagedObjectModel
通過NSManagedObjectModel台丛,可以將創(chuàng)建的數(shù)據(jù)模型文件讀取為模型管理類對(duì)象。實(shí)體類似于數(shù)據(jù)庫中的表結(jié)構(gòu)砾肺。
持久化存儲(chǔ)協(xié)調(diào)者類NSPersistentStoreCoordinator
NSPersistentStoreCoordinator建立數(shù)據(jù)模型與本地文件或數(shù)據(jù)庫之間的聯(lián)系挽霉,通過它將本地?cái)?shù)據(jù)讀入內(nèi)存或者將修改過的臨時(shí)數(shù)據(jù)進(jìn)行持久化的保存防嗡。
數(shù)據(jù)對(duì)象管理上下文NSManagedObjectContext
NSManagedObjectContext是進(jìn)行數(shù)據(jù)管理的核心類,我們通過這個(gè)類來進(jìn)行數(shù)據(jù)的增刪改查等操作炼吴。
如何使用
一般我們需要在工程里創(chuàng)建一個(gè)xxx.xcdatamodeld
文件本鸣。然后在這個(gè)文件里面創(chuàng)建表
創(chuàng)建NSManagedObjectModel對(duì)象
首先、我們需要知道數(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;
}
創(chuàng)建NSPersistentStoreCoordinator對(duì)象
PersistentStoreCoordinator對(duì)象的創(chuàng)建需要用到ManagedObjectModel對(duì)象,根據(jù)objectModel的模型結(jié)構(gòu)創(chuàng)建持久化的本地存儲(chǔ)童芹。同時(shí)PersistentStoreCoordinator對(duì)象需要知道數(shù)據(jù)庫文件在哪里涮瞻,以便打開對(duì)一個(gè)的數(shù)據(jù)庫。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// 指定一個(gè)數(shù)據(jù)庫文件路徑
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DDBITrackKit.sqlite"];
NSError *error = nil;
// 通過ManagedObjectModel對(duì)象創(chuàng)建NSPersistentStoreCoordinator對(duì)象假褪。
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// 指定底層的存儲(chǔ)方式為SQLite數(shù)據(jù)庫署咽。
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
//
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
創(chuàng)建NSManagedObjectContext對(duì)象。
操作數(shù)據(jù)需要用到Object的句柄生音,每個(gè)句柄管理了對(duì)應(yīng)的ObjectModel對(duì)象宁否。一般我們會(huì)創(chuàng)建一個(gè)主句柄。然后使用子句柄執(zhí)行多線程操作缀遍。
需要注意的點(diǎn)是:各個(gè)線程創(chuàng)建的子句柄相互之間是不能直接進(jìn)行通信的慕匠,后果是有可能會(huì)崩潰,以及查詢不出數(shù)據(jù)域醇、或者查詢出來錯(cuò)誤的數(shù)據(jù)台谊。如果需要通信,那么需要把實(shí)體(Entity)的ObjectID傳遞給另一個(gè)線程譬挚,然后這個(gè)線程里的context執(zhí)行對(duì)應(yīng)的查詢锅铅,然后再改變查詢出來的object的數(shù)據(jù)。比較繁瑣减宣。
保存數(shù)據(jù)的時(shí)候盐须。可以調(diào)用子句柄的save:
函數(shù)蚪腋,然后會(huì)在我們的通知中心里拿到這個(gè)數(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è)置一個(gè) persistent store coordinator 和兩個(gè)獨(dú)立的 contexts 被證明了是在后臺(tái)處理 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];
}];
}
}];
查詢數(shù)據(jù)
通過NSEntityDescription
來查詢所需要的實(shí)體對(duì)象立帖。
+ (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;
}
更新數(shù)據(jù)
先查詢出來對(duì)象,然后更新對(duì)象響應(yīng)的字段悠砚,最后調(diào)用save
函數(shù)進(jìn)行保存
- (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);
}
}
刪除對(duì)象
- (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];
}
mr 的內(nèi)部實(shí)現(xiàn)
- (BOOL) MR_deleteEntityInContext:(NSManagedObjectContext *)context
{
NSError *error = nil;
NSManagedObject *entityInContext = [context existingObjectWithID:[self objectID] error:&error];
[MagicalRecord handleErrors:error];
if (entityInContext) {
[context deleteObject:entityInContext];
}
return YES;
}
通過這里可以看到晓勇,線程之間傳遞數(shù)據(jù)是通過objectID
查詢出來對(duì)應(yīng)的實(shí)體的。然后調(diào)用context
的deleteObject
函數(shù)將這個(gè)實(shí)體刪除。