阅读完本书,首先给我的感觉是内容有点对不起它的¥59.80定价,全书主要讲了两块内容,一块是SQLite3,一块是CoreData,前者讲的是SQLite语句调用,后者讲的是CoreData的基础应用,至于分线程调用,线程的安全,数据迁移,升级,加密安全等皆没有涉及。实际情况是SQLite语句在实际项目中的使用率几乎为零,基本都使用FMDB类库来替代直接编写SQLite语句了,加上通篇80%的内容都在讲Storyboard等UI的创建和代码,正真能配得上“高级”二字的干货少之又少,不得不说清大出版社又水了一版经验。
我是不太喜欢去写技术类的博客的,倒不是吝啬,就技术而言,也没什么好吝啬的,都有从网上搬砖的阶段,藏着掖着也是要过时的。尽管我一直没有中断阅读技术类和其他类书籍,但是我一直不喜欢写书评或者技术博客的真正原因是,我事情比较多,除了工作还有家庭还有自己的生活云云。好了,废话不多说了,我想要表达的是,绩效考评上规定了我要写这个本书的Blog,就只能写了,不过既然写了,那就把自己知道的都讲出来吧。
一、FMDB
1、基础部分
1)建表
/*创建数据库
*/
- (void)initDatabase {
// 数据库文件路径 ,创建数据库
_sqlPath = 自定义的沙盒路径,通常会在App当前用户所在私有文件夹下,确保数据隔离
_db = [FMDatabase *db = [FMDatabase databaseWithPath:d_sqlPath];
[self createMessageTableWithDB:_db];
}
/* 建表
*/
- (void)createMessageTableWithDB:(FMDatabase *)db {
NSString *strSql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS '%@' (%@ INTEGER primary key autoincrement,%@ TEXT NOT NULL,%@ TEXT, %@ TEXT, %@ TEXT, %@ INTEGER);" ,
kTableMessage,
MSG_sqlId,
MSG_target,
MSG_fromLoginName,
MSG_toLoginName,
MSG_memberList......
];
if (![db executeUpdate:strSql]) {
NSAssert(0, @"消息表创建失败");
}
}
primary key autoincrement代表主键自增长模式,通常列名和表名标准采用:
static NSString * const MSG_sqlId = @"MSG_sqlId";
这样的方式比较标准,宏定义相对容易覆盖,且不易察觉;
2)插入数据
+ (void)insertWith:(Model *)model DB:(FMDatabase *)db {
NSString *sql = [NSString stringWithFormat:@"INSERT INTO '%@' (%@,%@,%@) VALUES ('%@','%d','%lld')", kTableSessionList,
C_NAME,
C_AGE,
C_TIMESTAMP,
model.name,
model.age,
model.timestamp];
// 插入数据库
if (![db executeUpdate:sql]){
NSAssert(0, @"插入更新消息数据到会话表失败");
}
}
3)更新数据
+ (void)updateWith:(Model *)model DB:(FMDatabase *)db {
NSString *sql = [NSString stringWithFormat:@"UPDATE '%@' set %@ = '%@',%@ = '%lld', %@ = '%ld' where %@ = '%@'",
kTableSessionList,
C_NAME,
C_AGE,
C_TIMESTAMP,
model.name,
model.age,
model.timestamp,
C_ID,
model.sqlid];
if (![db executeUpdate:sql]) {
NSAssert(0, @"更新消息数据到会话表失败");
}
}
4)删除数据
+ (void)deleteWith:(NSString *)uid DB:(FMDatabase *)db {
NSString *sql = [NSString stringWithFormat:@"delete from '%@' where %@ = '%@'",
kTableSessionList,
C_ID,
uid];
if (![db executeUpdate:sql]) {
NSAssert(0, @"删除对象的会话失败");
}
}
5)查询数据
+ (NSArray *)queryMessageListDataWithDB:(FMDatabase *)db {
NSMutableArray *arrMsgList = [NSMutableArray array];
NSString *strUser = [[MsgUserInfoMgr share] getUid];
NSString *strPickMessge = [NSString stringWithFormat:@"select * from '%@' where %@ != '%@' and %@ = '0' order by %@ desc",
kTableSessionList,
contact_target, strUser,
contact_isApp,
contact_timeStamp];
FMResultSet *result = [db executeQuery:strPickMessge];
while ([result next]) {
ContactDetailModel *model = [[ContactDetailModel alloc] init];
model._sqlId = [result intForColumn:contact_sqlid];
model._target = [result stringForColumn:contact_target];
model._nickName = [result stringForColumn:contact_nickName];
model._headPic = [result stringForColumn:contact_headpic];
model._content = [result stringForColumn:contact_content];
model._countUnread = [result intForColumn:contact_countUnread];
model._timeStamp = [result longLongIntForColumn:contact_timeStamp];
model._isGroup = [result boolForColumn:contact_isGroup];
model._isApp = [result boolForColumn:contact_isApp];
[arrMsgList addObject:model];
}
return arrMsgList;
}
6)字符转义
// SQLite插入特殊字符转义,否则插入过程会引起错误,主要是单引号的去除
+ (NSString *)convertSpecialCharInString:(NSString *)string {
if ([string isKindOfClass:[NSNull class]] || string == nil) {
return @"";
}
else {
NSString *oldString = string;
NSMutableString *newString = [NSMutableString string];
NSString *str_tmp;
NSRange range = [oldString rangeOfString:@"'"];
while (range.location != NSNotFound)
{
str_tmp = [oldString substringToIndex:range.location];
[newString appendString:str_tmp];
[newString appendString:@"''"];
oldString = [oldString substringFromIndex:range.location + 1];
range = [oldString rangeOfString:@"'"];
}
[newString appendString:oldString];
return newString;
}
}
2、事务
// 不采用事务的批处理,耗时严重,而且处理耗费时间随着批处理数量级呈几何级上升
[_fmdb open];
for (1W次)
{
// 数据操作......
}
[_fmdb close];
在iPhone4s上使用上述方式测试耗时30s+,同样的操作在下方代码中采用事务包装下耗时0.256s,在批量处理性能上差距是非常明显的,所以对于批量处理数据尽可能用事务封装。
// 批量插入会话表数据,采用事务封装可以大大节省时间,特别是在庞大的批处理数据量的时候,采用事务处理的速度几乎和单条处理一样快;同事,事务的另一特点是支持回滚操作。
- (BOOL)managerBatchData:(NSArray *)array {
[_fmdb open];
// 事务封装
[_fmdb beginTransaction];
BOOL isRollBack = NO;
@try
{
for (N次)
{
// 数据操作
if (失败)
{
isRollBack = YES;
}}}
@catch (NSException *exception)
{
isRollBack = YES;
}
@finally
{
if (!isRollBack) {[_fmdb commit];}
else {[_fmdb rollback];}
}
[_fmdb close];
return !isRollBack;
}
3、线程安全
如果我们的 app 需要多线程操作数据库,那么就需要使用 FMDatabaseQueue 来保证线程安全了。 切记不能在多个线程中共同对一个 FMDatabase 对象并且在多个线程中同时使用,这个类本身不是线程安全的,这样使用会造成数据混乱等问题。例如:线程1进行insert,线程2进行query,那结果就会不正确。
使用 FMDatabaseQueue 很简单,首先用一个数据库文件地址来初使化 FMDatabaseQueue,然后就可以将一个闭包 (block) 传入 inDatabase 方法中。 在闭包中操作数据库,而不直接参与 FMDatabase 的管理。
// 创建,最好放在一个单例的类中,除非你有足够理由和逻辑保障两个queue之间的操作不会引发问题,不然通常我们的数据量放在同一个queue中即可
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
// 使用
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while ([rs next]) {
// …
}
}];
// 如果要支持事务
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
if (whoopsSomethingWrongHappened) {
*rollback = YES;
return;
}
// etc…
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];
}];
但是问题来了,FMDatabaseQueue看起来如同字面一样是个Queue,这很容易让人联想到GCD中的多线程队列,容易让人产生这就是分线程的误区,下面是FMDatabaseQueue的FMDatabaseQueue方法源码——
- (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
FMDBRetain(self);
dispatch_sync(_queue, ^() {
BOOL shouldRollback = NO;
if (useDeferred) {
[[self database] beginDeferredTransaction];
}
else {
[[self database] beginTransaction];
}
block([self database], &shouldRollback);
if (shouldRollback) {
[[self database] rollback];
}
else {
[[self database] commit];
}
});
FMDBRelease(self);
}
事实上它只是做了dispatch_sync...,看到了吗,放在了同步队列中,FMDatabaseQueue只是单纯的将所有任务进行事务或者非事务方式压入了一个同步的队列中,也就是任务是手拉手排队按顺序进行,整个Queue依然在主线程中运行着,这在处理庞大数据群的时候会卡出翔。于是需要我们自己去把任务放到分线程去,创建一个(唯一)私有队列进行异步运行,搞定!
dispatch_queue_t myQueue;
dispatch_async(self.myQueue, ^{
// 数据库任务处理
dispatch_async(dispatch_get_main_queue(), ^{
// 回归主线程
});
});
4、数据版本
1)非正常插入数据
假设有一张表名为Table_A的表,列名有name、sex、age
DataOperateKind dataKind; // 进行3种数据插入试验
dataKind = kind_standard; // 插入name、sex、age字段数量正常(成功)
dataKind = kind_overstep; // 插入name、sex、age、home字段数量多了(失败)
dataKind = kind_shortage; // 插入name、sex字段数量少了(成功)
2)更改表结构
TableOperateKind operateKind; // 进行3种表操作
operateKind = kind_increase; // 增加属性名字,可以直接操作,但是不能多个列同时增加、列名不能重复
strMessage = [NSString stringWithFormat:@"alter table '%@' add column '%@' TEXT",
_strTableName, MSG_clientMsgId];
operateKind = kind_alter; // 更改属性名字
operateKind = kind_reduce; // 减少属性名字
上述两个操作都无法直接对表进行操作,需要变相操作,操作思路如下——
// 1、将原表名改为临时表
strMessage = [NSString stringWithFormat:@"alter table '%@' RENAME TO '%@'",
_strTableName,strTmp];
........
// 2、创建新表
strMessage = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS '%@' (%@ INTEGER primary key autoincrement,%@ TEXT,%@ TEXT,%@ TEXT)" ,_strTableName,
MSG_sqlId,MSG_target,MSG_content,MSG_msgId];
......
// 3、从旧表中导入数据(不带sqlId主键会发生什么)
strMessage = [NSString stringWithFormat:@"INSERT INTO '%@' ('%@','%@','%@') SELECT %@, %@, %@ FROM '%@'"
,_strTableName,
MSG_target,
MSG_msgId,
MSG_content,
MSG_target,
MSG_fromLoginName,
MSG_content,
strTmp];
// PS:如果原表和新创建的表列数一致,并且是一一对应的话,可以用一下语句导入。(insert into 'NewTable' select * from 'OldTable')
......
/* 4、更新sqlite_sequence
UPDATE "sqlite_sequence" SET seq = 3 WHERE name = 'Student';
由于在Sqlite中使用自增长字段,引擎会自动产生一个sqlite_sequence表,用于记录每个表的自增长字段的已使用的最大值,所以要一起更新下。如果有没有设置自增长,则跳过此步骤。
*/
// 5、删除临时表(可选)
strMessage = [NSString stringWithFormat:@"DROP TABLE '%@'" ,strTmp];
......
5、数据安全
如果我们想要使得自己的数据库加密,解决方案就是使用另一款开源的加密数据库SQLCipher,SQLCipher使用256-bit AES加密,由于其基于免费版的SQLite,主要的加密接口和SQLite是相同的,当然也增加了一些自己的接口,如在新建和打开数据库时,给数据库设置秘钥之类的操作。
FMDB是一个开源的类库,它对sqlite数据库操作进行了很不错的封装,而且也增加了对sqlcipher的支持,也就是说,我们不直接用sqlcihper也能完成加解密操作,而且FMDB在操作sqlite方面方便得多。现在的APP开发如果涉及到数据库操作,FMDB基本上是首选。
具有加密功能的FMDB版本
加密的FMDB其实是一个分支,也就是说,如果你需要替换FMDB。Github上关于该分支的安装只提供了cocospod的安装方式。Github上FMDB的地址:https://github.com/ccgus/fmdb
举个例子,如果你以前是如下写的
pod ‘FMDB‘
如果想换成有加密功能的,就改成
pod ‘FMDB/SQLCipher‘
升级数据库代码实现
得到对的版本后,我们需要在代码里做一些处理,让旧数据库升级。这里升级包括两点:
1 是我们前面所说的,将数据库加密。
2 把旧数据库的表和数据迁移到新数据库中。
具体的整理一个Demo,一看便知
链接: http://pan.baidu.com/s/1pKt2PL5 密码: akn8
PS:该Demo是某位同行做的,但是有错误,导致解密了无法解密,现在经过修改后可以正常使用,至于版权问题,在此申明转载并修改部分问题,出处如下——
// ViewController.m
// FMDBEncrypt
// Created by jasonwang on 15/11/18.
// Copyright © 2015年 jasonwang. All rights reserved.
关于FMDB就讲这么多了,后面会讲下CoreData的部分。
二、CoreData
关于CoreData技术,这本书给大家推荐,整书阅读起来都是比较轻松,一遍看不懂两遍一定可以看懂,配合自己写写Demo基本就能入门了,此书刚好在公司书架上。
一直有一个争论,就是SQLite3和CoreData我们选哪一个,我个人觉得这不是一个可以用一句话来讲清楚的事情,也完全不是JSONKit和NSJSONSerialization之间用哪个那般清晰。很多经验丰富尤其拥有极多采用了SQLite3方案的成功项目经验的程序员,总难免有一种优越感,认为能熟练驾驭SQLite3并且拥有完善的版本迁移、加密等成熟方案,为什么要去折腾CoreData技术,能找出十几个理由来辩论。包括我本人,在了解CoreData初期也有会困惑,为什么要去折腾那么复杂的东西(因为CoreData和传统的SQL模式相去甚远导致初次接触不那么友善),后来慢慢通过Demo等剖析,不得不承认,非常有必要使用CoreData去取代SQLite方案。我想到了一个极佳的比喻,SQLite3是手动挡汽车,而CoreData是自动挡汽车,后者在通用性易用性可维护性上可谓有绝对优势,而前者在汽车层面好歹还有个手动挡比较有乐趣这个说法,但是在开发上只有纯手动带来的庞大时间开支、精力维护和不易扩展等困难(一个表结构变动带来的版本问题就够折腾死了),那问题来了,为什么愿意守着一个相对技术要求比较高的东西而排斥去接受一个简单好用的新技术呢,答案是,当年花了太多心血和时间去学习并熟练掌握SQLite3技术解决方案,在好不容易经历了很多项目沉淀了些经验和解决方案想得意地笑时候,突然来了一个CoreData个东西把你这么多年积累的东西一股脑丢到了Apple的黑箱中,给你提供了几个简单的API,好像前面那么多努力都白干了,这叫什么事儿......
1、前言
传统的SQL模式,开发需要写UIData-->Model-->SQLManager-->FMDB-->SQL,而CoreData的核心意义在于直接将最复杂的SQLManager这层帮你做掉了,开发不再需要再去写和SQL层的逻辑,只需要关心业务逻辑即可。
当然CoreData本身的API依旧是非常不友好且沉重的,如同SQLite3的API一样,所以有人就做了改变,于是MagicalRecord这个好用的第三方类库就出现了,如同FMDB一样,让猿们又觉得世界美好了。鉴于实际开发过程中,直接使用CoreData的API的概率非常小,所以我就直接讲下MagicalRecord的使用吧,有兴趣研究CoreData原生API的可以自己去翻翻书。
2、基础
先简单的说一下coredata的五个模块:
1) Managed Object Model
Managed Object Model 是描述应用程序的数据模型,这个模型包含实体(Entity),特性(Property),读取请求(Fetch Request)等。(下文都使用英文术语。)
应用程序先创建或读取模型文件(后缀为xcdatamodeld)生成 NSManagedObjectModel 对象。Document应用程序是一般是通过 NSDocument 或其子类 NSPersistentDocument)从模型文件(后缀为 xcdatamodeld)读取。
2)Managed Object Context
Managed Object Context 参与对数据进行各种操作的整个过程,它持有 Managed Object。我们通过它来监测 Managed Object。监测数据对象有两个作用:支持 undo/redo 以及数据绑定。这个类是最常被用到的
3)Persistent Store Coordinator
Persistent Store Coordinator 负责从数据文件(xml, sqlite,二进制文件等)中读取数据生成 Managed Object,或保存 Managed Object 写入数据文件,处理底层的对数据文件的读取与写入。一般我们无需与它打交道。
4)Managed Object
Managed Object 数据对象,与 Managed Object Context 相关联。
5)Controller
一般都是通过 control+drag 将 Managed Object Context 绑定到它们,这样我们就可以在 nib 中可视化地操作数据。
模型有点像数据库的表结构,里面包含 Entry, 实体又包含三种 Property:Attribute(属性),RelationShip(关系), Fetched Property(读取属性)。Model class 的名字多以 "Description" 结尾。我们可以看出:模型就是描述数据类型以及其关系的。
3、MagicalRecord
1)安装
先使用cocoaPods导入MagicalRecord这个类库,不再赘述
2)创建模型文件
这一步不管你是否使用MagicalRecord都省不了,接下来的步骤如果看起来比较吃力,建议先补下相关基础,找个图文并茂的就清楚了。
创建一个名为Model的模型文件。 (File > New File… > Core Data > Data Model)
点击左下角的Add Entity,更改Entity的名字为Person。
为Entity添加三个Attribute:age(Integer16)、firstname(string)、lastname(string)。
点击Editor > Create NSManagedObject Subclass… > Create创建模型文件对应的类。
3)初始化MagicalRecord
首先在AppDelegate.m中添加以下代码对MagicalRecord进行初始化:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[MagicalRecord setupCoreDataStackWithStoreNamed:@"MyDatabase.sqlite"];
// ...
return YES;
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
[MagicalRecord cleanUp];
}
是否比Core Data默认的初始化简洁多了呢?
4)查询
使用Person的MR_findAll、MR_findAllSortedBy、MR_findByAttribute等方法可以查询Person:
//查找数据库中的所有Person。
NSArray *persons = [Person MR_findAll];
//查找所有的Person并按照first name排序。
NSArray *personsSorted = [Person MR_findAllSortedBy:@"firstname" ascending:YES];
//查找所有age属性为25的Person记录。
NSArray *personsAgeEuqals25 = [Person MR_findByAttribute:@"age" withValue:[NSNumber numberWithInt:25]];
//查找数据库中的第一条记录
Person *person = [Person MR_findFirst];
5)添加
使用Person的MR_createEntity方法可以方便的创建一个Person,需要使用[[NSManagedObjectContext MR_defaultContext] MR_save]来进行保存哦:
Person *person = [Person MR_createEntity];
person.firstname = @"Frank";
person.lastname = @"Zhang";
person.age = @26;//此处使用了LLVM的新特性,XCode 4.4可用
[[NSManagedObjectContext MR_defaultContext] MR_save];
6)更新
直接对数据库中查找到的Person进行赋值,然后使用NSManagedObjectContext保存即可更新Person:
Person *person = ...;//此处略
person.lastname = object;
[[NSManagedObjectContext MR_defaultContext] MR_save];
7)删除
使用Person的MR_deleteEntity可以方便的删除Person,模式和添加更新一致:
Person *person = ...;//此处略
[person MR_deleteEntity];
[[NSManagedObjectContext MR_defaultContext] MR_save];
4、模型对象的迁移和扩展
在SQL层面其实就是所谓表结构的变更(大部分都是增加表列名),之前采用SQL的时候,表结构变更通常伴随着数据的迁移,加上在版本间的适配和容错机制,需要非常多的代码量,于是有经验的程序员会在初次建表的时候加入几个备用的列,并且在极端情况下列名不够用时,采用JSON来多个对象压缩成NSString再存储,而CoreData自带的模型迁移机制统统帮你承担了这些工作,开发所需要做的就仅仅是增加你所需要的列名,然后选择迁移机制即可,剩下的版本控制什么的都交给CoreData吧。
1)轻量级迁移:
大部分列名的增减都可以采用次模式,遇到交叉列名,比如旧模型中的A属性要映射到新模型中的B属性,此迁移模式会自动推断并迁移,但是可能会推测错误。
2)默认迁移:
比轻量级迁移模式更精确的受控,开发可以自己指定需要迁移的属性映射。
3)迁移管理器:
包含迁移进度报告等更多的特性,例如微信遇到大版本需要数据迁移的时候,会播放一段音乐或者当年那个打飞机亦是为此而生。
5、驱动表视图
假设一个UITableView列表所对应的数据库表拥有上万条乃至更多,常用的显示方式分批加载,用户通过下拉或者上拉来加载更多数据,那么为了避免NSMutableArray越来越臃肿,负责的程序员会去编写有限的数据显示,就是精确地控制NSMutableArray始终保留必要的一段数据区间,不至于让内存暴涨,那么要写好一段好的数据区间是很难的,而且需要反复测试。
而在CoreData里这些都不叫事了,直接只要[setFetchBatchSize:N条] 就可以搞定了,这让花了一天时间写数据区间并精确测试性能的老猿情以何堪......
6、分线程
CoreData是支持分线程的,这块具体的不再赘述;小规模的数据库普通的后台机制就可以搞定,大规模数据IO的需要使用GCD等多线程机制来处理。
7、支持iCloud
CoreData集成了iCloud的开发,使用非常方便,鉴于使用机会不多,再次不再赘述。
三、总结
对于CoreData和SQLite3到底用哪一个,一直都没有一个定论,只能说事物都有双面性,你使用CoreData享受了不干脏活累活,你也得忍受它在超大数据量压力下的迟缓和不灵活;你想要精确的操控性以及灵活的身姿,那么你得花很大的精力去手动编写很多代码并且测试。
关键的一点就是,如果你的项目本地化数据量和交换业务相当庞大,并且非常讲究时效性和精确性,需要极高的多线程处理能力,那么建议使用FMDB进行手动控制;相反如果你的数据业务非常简单,建议使用CoreData,省时省力。
我从知乎上收集了一些同行的看法,分享给大家,就当做个总结。
megan zhou,积跬步乃至千里,做好小事乃成大事。
bc wang、hyman、知乎用户 等人赞同
我经常听人说,sqlite直接操作数据库,直接简单,效率也高。
但作为一个面向对象的开发者,coredata对我具有无限的吸引里。
真心喜欢它把数据模型都封装成对象,从开发的效率和代码的角度,我觉得coredata是如此的优雅。
另外,很多人在初始化coredata的数据的时候经常使用dictionary的setvalue:forkey方法,可伴随着学习的不断深入,我发现直接使用object.property = value就可以初始化mode。关键看你怎么封装了。
我想这也许是苹果极力推荐coredata的缘故吧。
coredata对于数据的处理能力还是很强的,你实在没有办法也可以结合sqlite一起使用。这就是所谓的原生sql吧。毕竟coredata是对于sqlite的封装。只是使用原生的时候要注意,coredata生成的字段前面都有个z,这个你查看一下表结构就可以很清晰的看到。
知乎用户,iPhone Developer
张翼飞、孙超、everettjf 等人赞同
首先,coredata和sqlite的概念不同,core为对象周期管理,而sqlite为dbms。
下面的讨论以使用core data来做数据持久化并使用sqlite做backend存储的情况为前提。
使用方便性。实际上,一个成熟的工程中一定是对数据持久化进行了封装的,因此底层使用的到底是core data还是sqlite,不应该被业务逻辑开发者关心。因此,即使习惯写SQL查询的人,也应该避免在业务逻辑中直接编写SQL语句。
存储性能,在写入性能上,因为都是使用的sqlite格式作为磁盘存储格式,因此其性能是一样的,如果你觉得用core data写的慢,很可能是你用sqlite的时候写的每条数据的内容没有core data时多,或者是你批量写入的时候每写入一条就调用了一次save。
查询性能,core data因为要兼容多种后端格式,因此查询时,其可用的语句比直接使用sqlite少,因此有些fetch实际上不是在sqlite中执行的。但这样未必会降低查询效率。因为iPhone的flash memory速度还是很快的。我的经验是大部分时候,在内存不是很紧张时,直接fetch一个entity的所有数据然后在内存中做filter往往比使用predicate在fetch时过滤更快。如果你觉的查询慢,很可能是查询方式有问题,可以把core data的debug模式打开,看一下到底执行了多少SQL语句,相信其中大部分是可以通过改写core data的调用方式避免的。
core data的一个比较大的痛点是多人合作开发的时候,管理coredata的模型需要很小心,尤其是合并的时候,他的data model是XML格式的,手动resolve比较烦心。
core data还有其他sql所不具备的优点,比如对undo的支持,多个context实现sketchbook类似的功能。为ManagedObject优化的row cash等。
另外core data是支持多线程的,但需要thread confinement的方式实现,使用了多线程之后可以最大化的防止阻塞主线程。
张俊,iOS开发
weiyu、知乎用户 赞同
1.如果你的项目规模比较大,用coreData 可以减少你对存储管理的很多工作,否则你可能需要自己写很多的数据模型倒数据库操作的代码。
2.你的数据结构变化,数据迁移的时候coreData能帮你自动的完成,用sqlite 你就需要自己写代码来完成。
3.coreData还有些其他效率方面的优化,比如延迟写入。
对我自己而言,一般来说,如果没有复杂的 查询 需求,而数据量又比较小的话,我会用文件来做存储,自我感觉比较干净。如果涉及到比较多的数据,但是结构比较单一,表比较少,逻辑比较简单用sqlite也不错,但是如果你的表比较多,操作也比较多,还有升级迁移的需求,推荐使用coreData吧。
孙竟,如何把彷徨,写出情节来…
台VV 赞同
印象中(不知道后来更新没),CoreData 没有批处理。经我测试,批量插入、更新和删除数据时,比 SQLite 慢 2 个数量级。
举例来说,批量更新 1000 条数据,CoreData 用了 4 秒,SQLite 不到 0.1 秒。
再以 iReadG 为例,标记 100 条项目为已读并载入下 100 条未读项目要 10 多秒,期间啥都不能干。这种效率你能忍么?
当然,如果你的数据量小到任何数据库操作都不会让用户等待超过 1 秒,那就随意。
HUANG Jerry
噼啪boy、何镇威、游者 等人赞同
我以前的公司有自己的针对iphone的sqlite处理库(我自己开发的=w=),操作简单,功能比较完整,连数据迁移的功能都有,而且也有针对公司业务的具体情况。所以完全没有动力用coredata。
我记得我面试公司的时候老板问我(原本是想做JAVA的),“关于Hibernate,你觉得它的好处是什么?”。我很快就将标准答案答出来了,无非就是说数据处理的对象化。然后他接着问,为什么要将数据处理对象化。我大概说,因为这样可以加强代码的统一性等等。当时我是毕业生,并不明白他的用意,后来我知道了。在很多开发,这种代码的性质上的统一可能真的不是特别 实际。
我们公司做的程序有个特点,就是数据库版本更新特别多。最多纪录是半年内对一个app的数据库进行各种更新合共将近30次。此外还有其他一些特点:
为避免防止断电,程序出错、退出等各种状况造成数据不完整,每次数据有任何微小变动都要进行数据库写入,不过更重要的其实是直接用SQLite更新数据所动用的运算太少了,所以即使每次变动都更新问题都不大,倒也不是真的要做的万无一失。
数据量庞大而且增速快。每次日常基准操作大概会造成200-500条数据插入。一天大概要进行2-5次基准操作。这些操作会上传到服务器,而其他用户也能通过服务器下载,所以数据库里很容易看到某个表出现几十万条数据。
这种业务内容coredata其实是比较难以应付的。
当年老板告诉我他对对象化处理数据持久的看法。简单概括是“不灵活”。这也是我现在对CoreData的想法。
我举个例子吧,譬如说数据迁移,数据库的表格要变动了,这在CoreData的处理还是太过繁琐。而用我们公司的库,这仅仅是加入一个xml文件,里面存储了几条更新数据库的SQL而已。无论是删表还是加列加表都相当方便。xml有自己的版本,程序在启动的时候会检查数据库的版本和xml的版本而决定是否执行相关的SQL。这种做法带来的另外一个好处是,数据库的变动很容易追踪。CoreData的Model虽然直观,但其实比起那几条SQL,说实在,你能一下看出两个xml的SQL到底对数据库做了什么修改,但你未必能一眼看出两个CoreData的Model有什么不同。30个放一起,你想追踪这半年来你的数据库的变动,用CoreData的话会非常痛苦。
再有一个例子,像前面所说的,某个表已经有几十万个数据,然后要为这个表做些修改,譬如增加一个列,而列中的数据是基于其他表和这个表的一些数据进行初始化。CoreData要处理这种业务就相当困难。原本CoreData是要防止程序中加入太多数据处理代码,这种情况下CoreData反而因为不够灵活而必须涉及很多数据代码,这样的对象化数据处理真的有必要吗?相比之下SQL要处理这些问题可谓手到擒来。
但无论如何,我现在正在学用CoreData,主要是因为CoreData毕竟有它合适的地方。对于很多App来说,我们公司的那种App那样的数据业务状况是不常见的,CoreData在这个情况下还是比较方便的。毕竟不是每个公司都像我以前的公司那样构造一个完整的SQLite处理库,CoreData在这种情况下也算是一种通用语言。不过我必须说,CoreData用起来还是不够灵活。
AirSpuer,开始回答问题。
金恩、何健源、知乎用户 等人赞同
coredata,建立的表没有主键,添加时都要自己处理。
还有它不是关系型数据库,处理多对多的关系时比较麻烦。
写到这里,也差不多了,能力有限,能说的也就这些了。有不正确的地方欢迎指出,谢谢~