在ios开发过程中,经常需要用到数据持久化工作。对于基本的配置信息等,NSUserDefault已经可以满足要求,但是对于大部分需要存储的信息,主要的方式有coredata建模或者sqlite写数据库的方法进行存储。现在针对coredata、sqlite以及常用的sqlite封装库进行研究和学习。
首先,针对四种数据持久化方式,进行基本的10w条数据插入得到性能耗时如下(由于sqlitepersistentobject未找到合适的批量插入方法,所以没有进行对比):
从中可以看出,sqlite最快,基本都只用fmdb以及coredata等的一半时间。接下来我们逐个解析相关技术/库的操作以及使用。
【coreData】
coredata相信基本都不陌生,我们常用的主要就是Data Model了,有两种方法添加Data Model:新建工程时勾选或者添加Core Data->Data Model文件。采用第一种方法默认会有相关的代码生成,为了更好的了解Core Data是怎么load进来的,我们采用第二种方法进行演示。
首先,我们添加CoreData->Model Data模型,名字输入为coreData,即可在文件列表中见到coreData.xcdatamodeld文件。我们先添加如下Entities:
我们已经有了模型,那么该怎样使用呢。有一个NSManagedObjectModel的类,专门用来管理数据模型的。先从coreData.xcdatamodeld中初始化模型:
1 |
<span style="font-family:Microsoft YaHei;">// initilize NSURL* modelURL = [[NSBundle mainBundle] URLForResource:@"coreData" withExtension:@"momd"]; coreDataModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];</span> |
接下来,我们需要考虑,有了模型后,数据最终应该存在哪里?答案是:文件。接下来我们有另外一个类来管理模型跟文件之间的对应关系:
1 |
<span style="font-family:Microsoft YaHei;">NSString* strInfoPath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"coreData.sqlite"]; coreDataCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:coreDataModel]; [coreDataCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:strInfoPath] options:nil error:nil];</span> |
1 |
<span style="font-family:Microsoft YaHei;">// 对context进行操作 coreDataContext = [[NSManagedObjectContext alloc] init]; [coreDataContext setPersistentStoreCoordinator:coreDataCoordinator];</span> |
1 |
<span style="font-family:Microsoft YaHei;">NSManagedObject* object = [NSEntityDescription insertNewObjectForEntityForName:@"TestCoreData" inManagedObjectContext:coreDataContext]; [object setValue:[NSNumber numberWithInt:data->intType] forKey:@"intType"]; [object setValue:[NSNumber numberWithFloat:data->floatType] forKey:@"floatType"]; [object setValue:[NSNumber numberWithDouble:data->doubleType] forKey:@"doubleType"]; [object setValue:[NSString stringWithUTF8String:data->testString] forKey:@"stringType"];</span> |
【sqlite】
1 |
<span style="font-family:Microsoft YaHei;">- (IBAction)onBtnSqlite:(id)sender { // 初始化数据库要保存的地方,如果存在则删除 NSString* strSQLiteFilePath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"sqlite.sqlite"]; BOOL bIsDir = FALSE; if ([[NSFileManager defaultManager] fileExistsAtPath:strSQLiteFilePath isDirectory:&bIsDir]) { [[NSFileManager defaultManager] removeItemAtPath:strSQLiteFilePath error:nil]; } sqlite3* sqlite = NULL; // 首先打开数据库路径,如果不存在则创建 if (SQLITE_OK != sqlite3_open([strSQLiteFilePath UTF8String], &sqlite)) { NSLog(@"sqlite3: open error..."); } // create table // 创建表,主要就是sql语句 NSString* strCreateTable = @"CREATE TABLE TESTCOREDATA(intType INTEGER, floatType FLOAT, doubleType DOUBLE, stringType VARCHAR(256))"; if (sqlite3_exec(sqlite, [strCreateTable UTF8String], nil, nil, nil) != SQLITE_OK) { NSLog(@"sqlite Create table error..."); } // 接下来是生成10w条测试数据 NSArray* arrayTest = [self arrayWithData:100000]; NSLog(@"Before save..."); // !!!这里很重要,将所有的insert操作作为一个transaction操作,这样避免每次insert的时候都去写文件,导致IO时间拖慢整个数据插入操作 NSString* strBegin = @"BEGIN TRANSACTION"; sqlite3_exec(sqlite, [strBegin UTF8String], NULL, NULL, NULL); // 遍历数据并插入,就是普通的sql语句操作 for (NSValue* value in arrayTest) { Data* data = [value pointerValue]; NSString* strSQLInsert = [NSString stringWithFormat:@"INSERT INTO TESTCOREDATA(intType, floatType, doubleType, stringType) values(%d, %f, %lf, '%s')", data->intType, data->floatType, data->doubleType, data->testString]; if (SQLITE_OK != sqlite3_exec(sqlite, [strSQLInsert UTF8String], NULL, NULL, NULL)) { const char* errormsg = sqlite3_errmsg(sqlite); NSLog(@"exec Error..."); } free(data); } // 提交所有的插入操作 NSString* strEnd = @"COMMIT"; sqlite3_exec(sqlite, [strEnd UTF8String], NULL, NULL, NULL); NSLog(@"End Save..."); // 不使用的时候关闭即可 sqlite3_close(sqlite); sqlite = NULL; }</span> |
1 |
<span style="font-family:Microsoft YaHei;">- (IBAction)onBtnSqliteRead:(id)sender { sqlite3* sqlite = NULL; NSString* strSQLiteFilePath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"sqlite.sqlite"]; BOOL bIsDir = FALSE; if (![[NSFileManager defaultManager] fileExistsAtPath:strSQLiteFilePath isDirectory:&bIsDir]) { NSLog(@"Sqlite Open Error....File NOt exist..."); return; } if (SQLITE_OK != sqlite3_open([strSQLiteFilePath UTF8String], &sqlite)) { NSLog(@"sqlite open error..."); return; } NSString* strSQL = @"select * from TESTCOREDATA"; sqlite3_stmt* stmt; // 将对应的操作信息跟stmt进行bind,如果有相关条件可以在prepare之后进行调整 sqlite3_prepare_v2(sqlite, [strSQL UTF8String], -1, &stmt, NULL); // 获取执行SQL的返回结果 while (SQLITE_ROW == sqlite3_step(stmt)) { int nIntType = sqlite3_column_int(stmt, 0); float floatType = sqlite3_column_double(stmt, 1); double doubleType = sqlite3_column_double(stmt, 2); const unsigned char* strTest = sqlite3_column_text(stmt, 3); break; } sqlite3_close(sqlite); } </span> |
【FMDB】
1 |
<span style="font-family:Microsoft YaHei;">NSString* strSQLiteFilePath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"fmdb.sqlite"]; BOOL bIsDir = FALSE; if ([[NSFileManager defaultManager] fileExistsAtPath:strSQLiteFilePath isDirectory:&bIsDir]) { [[NSFileManager defaultManager] removeItemAtPath:strSQLiteFilePath error:nil]; } FMDatabase* db = [FMDatabase databaseWithPath:strSQLiteFilePath]; if (![db open]) { NSLog(@"db Open Error..."); } NSString* strCreateTable = @"CREATE TABLE TESTCOREDATA(intType INTEGER, floatType FLOAT, doubleType DOUBLE, stringType VARCHAR(256))"; [db executeUpdate:strCreateTable]; NSLog(@"begin "); [db beginTransaction]; NSArray* arrayTest = [self arrayWithData:100000]; for (NSValue* value in arrayTest) { Data* data = [value pointerValue]; NSString* strSQLInsert = [NSString stringWithFormat:@"INSERT INTO TESTCOREDATA(intType, floatType, doubleType, stringType) values(%d, %f, %lf, '%s')", data->intType, data->floatType, data->doubleType, data->testString]; [db executeUpdate:strSQLInsert]; free(data); } [db commit]; NSLog(@"end..."); [db close]; db = nil;</span> |
1 |
<span style="font-family:Microsoft YaHei;">/** Return `int` value for query @param query The SQL query to be performed. @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. @return `int` value. */ - (int)intForQuery:(NSString*)query, ...; /** Return `long` value for query @param query The SQL query to be performed. @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. @return `long` value. */ - (long)longForQuery:(NSString*)query, ...;</span> |
1 |
[queue inDatabase:^(FMDatabase *db) { // 操作db }]; |
而由queue自己去保证执行的先后顺序和唯一性,避免同时操作时产生冲突等。
FMDatabasePool*则提供了一个db池,每一个db实力都会放在FMDatabasePool的池中,使用完成后归还db即可,sqlite相关的生命周期都由db池进行管理。避免经常性的open和close操作。
具体的相关细节可以参考fmdb的源码。
【sqlitepersistentobject】
1 |
<span style="font-family:Microsoft YaHei;">#import <Foundation/Foundation.h> #import "SQLitePersistentObject.h" @interface ZJSqlitePersistentobjectsPerson : SQLitePersistentObject { int intType; float floatType; double doubleType; NSString* stringType; } @property(assign, nonatomic) int intType; @property(assign, nonatomic) float floatType; @property(assign, nonatomic) double doubleType; @property(copy, nonatomic) NSString* stringType; @end</span> |
1 |
<span style="font-family:Microsoft YaHei;">NSArray* arrayTest = [self arrayWithData:1000]; NSLog(@"begin..."); for (NSValue* value in arrayTest) { Data* data = [value pointerValue]; ZJSqlitePersistentobjectsPerson* person = [[ZJSqlitePersistentobjectsPerson alloc] init]; person.intType = data->intType; person.floatType = data->floatType; person.doubleType = data->doubleType; person.stringType = [NSString stringWithUTF8String:data->testString]; [person save]; [person release]; free(data); } // [ZJSqlitePersistentobjectsPerson clearCache]; [ZJSqlitePersistentobjectsPerson clearCache]; NSLog(@"end..."); </span> |