凭良心讲,我不能告诉你不去使用Core Data。它不错,而且也在变好,并且它被很多其他Cocoa开发者所理解,当有新人加入你的组或者需要别人接手你的项目的时候,这点很重要。
更重要的是,不值得花时间和精力去写自己的系统去代替它。真的,使用Core Data吧。
为什么我不使用Core Data
Mike Ash写到:就我自己而言,我不是个狂热粉丝。我发现API是笨拙的,并且框架本身对于大量的数据是极其缓慢的。
一个实际的例子:10,000条目
想象一个RSS阅读器,一个用户可以在一个feed上点击右键,并且选择标记所有为已读。
引擎下,有一个带有read属性的Article实体。把所有条目标记为已读,程序需要加载这个feed的所有文章(可能通过一对多的关系),然后设置read属性为YES。
大部分情况下这样没关系。但是设想那个feed里有200个文章,为了避免阻塞主线程,你可能考虑在后台线程里做这个工作(特别当你的程序是一个iPhone应用)。当你一开始使用Core Data多线程,事情就开始变的不好处理了。
这可能还凑合,至少不值得切换走Core Data。
但是接下来加同步。
我用过两种不同的获取已读文章ID列表的RSS同步接口。其中一个返回近10,000个ID。
你不会打算在主线程中加载10,000个文章,然后设置read为NO。你甚至不想在后台线程里加载10,000个文章,即使很小心的管理内存,这有太多的工作(如果你频繁的这么做,想一下对电池寿命的影响)。
你真正想要做的是,让数据库给在ID列表里的每一个文章设置read为YES。
SQLite可以做到这个,只用一次调用。假设uniqueID上有索引,这会很快。而且你可以在后台线程执行像在主线程执行一样容易。
另一个例子:快速启动
我想减少我的另一个程序的启动时间,不只是开始的时间,而是在数据显示之前的所有时间。
那是个类似Twitter的应用(虽然它不是),它显示消息的时间轴。显示时间轴意味着获取消息,加载相关用户。它很快,但是在启动的时候,会填充UI,然后填充数据。
关于iPhone的应用(或者所有应用)我的理论是,启动时间很重要,比其他大部分开发者想的都要重要。应用的启动很慢看起来不像是要启动一样,因为人们潜意识里记得,并且会产生阻止启动应用的想法。减少启动时间就减少了摩擦,让用户更有可能继续使用你的应用,并且推荐给其他人。这是你让你的应用成功的一部分。
因为我不使用Core Data,我手边有一个简单的,保守的解决方案。我把timeline(消息和人物对象)通过NSCoding保存到一个plist文件中。启动的时候它读这个文件,创建消息和人物对象,UI一出现就显示时间轴。
这明显的减少了延迟。
把消息和人物对象作为NSManagedObject的实例对象,这是不可能的。(假设我有编码的并且存储的IDs对象,但是那意味着读plist然后触及数据库。这种方式我完全避免了数据库)。
在更新更快的机器出来后, 我去掉了那些代码。回顾过去,我希望我可以把它留下来。
我怎么考虑这个问题
当考虑是否使用Core Data时,我考虑下面这些事情:
会有难以置信数量的数据吗?
对于一个RSS阅读器或者Twitter应用,答案显而易见:是的。有些人关注上百个人。一个人可能订阅了上千个feeds。
即使你的应用不从网络获取数据,仍然有可能让用户自动添加数据。如果你用一个支持AppleScript的Mac,有些人会写脚本去加载非常多的数据。如果通过web API去加数据也是一样的。
会有一个Web API包含类似于数据库的终端吗(对比类对象终端)?
一个RSS同步API能够返回一个已读文章的uniquelIDs列表。一个记笔记的应用的一个同步API可能返回已存档的和已删除的笔记的uniquelIDs。
用户可能通过操作处理大量对象吗?
在底层,需要考虑和之前一样的问题。当有人删除所有下载的5,000个面食食谱,你的食谱应用可以多好的完成这个功能(在iPhone上?)?
当我决定使用Core Data(我已经发布过使用Core Data的应用),我会小心留意我怎么使用它。为了得到好的性能,我发现我把它当做一个SQL数据库的一个奇怪接口来使用,然后我知道我应该舍弃Core Data,直接使用SQLite。
我怎么使用SQLite
我通过FMDB Wrapper来使用SQLite,FMDB来自Flying Meat Software,由Gus Mueller提供。
基本操作
我在iPhone以前,Core Data以前就使用过SQLite。这是它怎么工作的的要点:
- 所有数据库访问-读和写-发生在连续的队列里,在一个后台线程。在主线程中触及数据库是从来不被允许的。使用一个连续队列来保证每一件事是按顺序发生的。
- 我大量使用blocks来让异步程序容易点。
- 模型对象只存在在主线程(但有两个重要的例外),改变会触发一个后台保存。
- 模型对象列出来他们在数据库中存储的属性。可能在代码里或者在plist文件里。
- 一些模型对象是唯一的,一些不是。取决于应用的需要(大部分情况是唯一的)。
- 对关系型数据,我尽可能避免连表查询。
- 一些对象类型在启动的时候就完全读入内存,另一些对象类型可能只需要创建并维护一个他们的uniqueIDs的。NSMutableSet,所以不需要去触及数据库,我就知道已经有什么。
- Web API的调用发生在后台线程,他们使用分开的模型对象。
我会通过我现在的应用的代码来详细描述。
数据库更新
在我最近的应用中,有一个单一的数据库控制器-VSDatabaseController,它通过FMDB来与SQLite对话。
FMDB区分更新和查询。更新数据库,app调用:
1 |
-[VSDatabaseController runDatabaseBlockInTransaction:(VSDatabaseUpdateBlock)databaseBlock] |
VSDatabaseUpdateBlock很简单:
1 |
typedef void (^VSDatabaseUpdateBlock)(FMDatabase *database); |
runDatabaseBlockInTransaction也很简单:
1 2 3 4 5 6 7 8 9 |
- (void)runDatabaseBlockInTransaction:(VSDatabaseUpdateBlock)databaseBlock { dispatch_async(self.serialDispatchQueue, ^{ @autoreleasepool { [self beginTransaction]; databaseBlock(self.database); [self endTransaction]; } }); } |
(注意我用自己的连续调度队列。Gus建议看一下FMDatabaseQueue,也是一个连续调度队列。我还没能去看一下,因为它比FMDB的其他东西都要新。)
beginTransaction和endTransaction的调用是可嵌套的(在我的数据库控制器里)。在合适的时候他们会调用-[FMDatabase beginTransaction]
和 -[FMDatabase commit]。(使用事务是让SQLite变快的关键。)提示:我把当前事务存储在
-[NSThread threadDictionary]。它很好获取每一个线程的数据,我几乎从不用其他的。
这儿有个调用更新数据库的简单例子:
1 2 3 4 5 6 7 |
- (void)emptyTagsLookupTableForNote:(VSNote *)note { NSString *uniqueID = note.uniqueID; [self runDatabaseBlockInTransaction:^(FMDatabase *database) { [database executeUpdate: @"delete from tagsNotesLookup where noteUniqueID = ?;", uniqueID]; }]; } |
这说明一些事情。首先SQL不可怕。即使你从没见过它,你也知道这行代码做了什么。
像VSDatabaseController的所有其他公共接口,emptyTagsLookupTableForNote应该在主线程中被调用。模型对象只能在主线程中被引用,所以在block中用uniqueID,而不是VSNote对象。
注意在这种情况下,我更新了一个查找表。Notes和tags是多对多关系,一种表现方式是用一个数据库表映射note uniqueIDs和tag uniqueIDs。这些表不会很难维护,但是如果可能,我确实尝试避免他们的使用。
注意在更新字符串中的?。-[FMDatabase executeUpdate:]
是一个可变参数函数。SQLite支持使用占位符?,所以你不需要把正真的值放入字符串。这儿有一个安全问题:它帮助守护程序反对SQL插入。如果你需要避开某些值,它也为你省了麻烦。
最后,在tagsNotesLookup表中,有一个noteUniquelID的索引(索引是SQLite性能的又一个关键)。这行代码在每次启动时都调用:
1 2 |
[self.database executeUpdate: @"CREATE INDEX if not exists noteUniqueIDIndex on tagsNotesLookup (noteUniqueID);"]; |
数据库获取
要获取对象,app调用:
1 2 3 |
-[VSDatabaseController runFetchForClass:(Class)databaseObjectClass fetchBlock:(VSDatabaseFetchBlock)fetchBlock fetchResultsBlock:(VSDatabaseFetchResultsBlock)fetchResultsBlock]; |
这两行代码做了大部分工作:
>翻译组。 凭良心讲,我不能告诉你不去使用Core Data。它不错,而且也在变好,并且它被很多其他Cocoa开发者所理解,当有新人加入你的组或者需要别人接手你的项目的时候,这点很重要。 更重要的是,不值得花时间和精力去写自己的系统去代替它。真的,使用Core Data吧。
为什么我不使用Core DataMike Ash写到:就我自己而言,我不是个狂热粉丝。我发现API是笨拙的,并且框架本身对于大量的数据是极其缓慢的。 一个实际的例子:10,000条目 想象一个RSS阅读器,一个用户可以在一个feed上点击右键,并且选择标记所有为已读。 引擎下,有一个带有read属性的Article实体。把所有条目标记为已读,程序需要加载这个feed的所有文章(可能通过一对多的关系),然后设置read属性为YES。 大部分情况下这样没关系。但是设想那个feed里有200个文章,为了避免阻塞主线程,你可能考虑在后台线程里做这个工作(特别当你的程序是一个iPhone应用)。当你一开始使用Core Data多线程,事情就开始变的不好处理了。 这可能还凑合,至少不值得切换走Core Data。 但是接下来加同步。 我用过两种不同的获取已读文章ID列表的RSS同步接口。其中一个返回近10,000个ID。 你不会打算在主线程中加载10,000个文章,然后设置read为NO。你甚至不想在后台线程里加载10,000个文章,即使很小心的管理内存,这有太多的工作(如果你频繁的这么做,想一下对电池寿命的影响)。 你真正想要做的是,让数据库给在ID列表里的每一个文章设置read为YES。 SQLite可以做到这个,只用一次调用。假设uniqueID上有索引,这会很快。而且你可以在后台线程执行像在主线程执行一样容易。 另一个例子:快速启动 我想减少我的另一个程序的启动时间,不只是开始的时间,而是在数据显示之前的所有时间。 那是个类似Twitter的应用(虽然它不是),它显示消息的时间轴。显示时间轴意味着获取消息,加载相关用户。它很快,但是在启动的时候,会填充UI,然后填充数据。 关于iPhone的应用(或者所有应用)我的理论是,启动时间很重要,比其他大部分开发者想的都要重要。应用的启动很慢看起来不像是要启动一样,因为人们潜意识里记得,并且会产生阻止启动应用的想法。减少启动时间就减少了摩擦,让用户更有可能继续使用你的应用,并且推荐给其他人。这是你让你的应用成功的一部分。 因为我不使用Core Data,我手边有一个简单的,保守的解决方案。我把timeline(消息和人物对象)通过NSCoding保存到一个plist文件中。启动的时候它读这个文件,创建消息和人物对象,UI一出现就显示时间轴。 这明显的减少了延迟。 把消息和人物对象作为NSManagedObject的实例对象,这是不可能的。(假设我有编码的并且存储的IDs对象,但是那意味着读plist然后触及数据库。这种方式我完全避免了数据库)。 在更新更快的机器出来后, 我去掉了那些代码。回顾过去,我希望我可以把它留下来。
我怎么考虑这个问题当考虑是否使用Core Data时,我考虑下面这些事情: 会有难以置信数量的数据吗? 对于一个RSS阅读器或者Twitter应用,答案显而易见:是的。有些人关注上百个人。一个人可能订阅了上千个feeds。 即使你的应用不从网络获取数据,仍然有可能让用户自动添加数据。如果你用一个支持AppleScript的Mac,有些人会写脚本去加载非常多的数据。如果通过web API去加数据也是一样的。 会有一个Web API包含类似于数据库的终端吗(对比类对象终端)? 一个RSS同步API能够返回一个已读文章的uniquelIDs列表。一个记笔记的应用的一个同步API可能返回已存档的和已删除的笔记的uniquelIDs。 用户可能通过操作处理大量对象吗? 在底层,需要考虑和之前一样的问题。当有人删除所有下载的5,000个面食食谱,你的食谱应用可以多好的完成这个功能(在iPhone上?)? 当我决定使用Core Data(我已经发布过使用Core Data的应用),我会小心留意我怎么使用它。为了得到好的性能,我发现我把它当做一个SQL数据库的一个奇怪接口来使用,然后我知道我应该舍弃Core Data,直接使用SQLite。
我怎么使用SQLite我通过FMDB Wrapper来使用SQLite,FMDB来自Flying Meat Software,由Gus Mueller提供。 基本操作 我在iPhone以前,Core Data以前就使用过SQLite。这是它怎么工作的的要点:
我会通过我现在的应用的代码来详细描述。 数据库更新 在我最近的应用中,有一个单一的数据库控制器-VSDatabaseController,它通过FMDB来与SQLite对话。 FMDB区分更新和查询。更新数据库,app调用:
VSDatabaseUpdateBlock很简单:
runDatabaseBlockInTransaction也很简单:
(注意我用自己的连续调度队列。Gus建议看一下FMDatabaseQueue,也是一个连续调度队列。我还没能去看一下,因为它比FMDB的其他东西都要新。) beginTransaction和endTransaction的调用是可嵌套的(在我的数据库控制器里)。在合适的时候他们会调用 这儿有个调用更新数据库的简单例子:
这说明一些事情。首先SQL不可怕。即使你从没见过它,你也知道这行代码做了什么。 像VSDatabaseController的所有其他公共接口,emptyTagsLookupTableForNote应该在主线程中被调用。模型对象只能在主线程中被引用,所以在block中用uniqueID,而不是VSNote对象。 注意在这种情况下,我更新了一个查找表。Notes和tags是多对多关系,一种表现方式是用一个数据库表映射note uniqueIDs和tag uniqueIDs。这些表不会很难维护,但是如果可能,我确实尝试避免他们的使用。 注意在更新字符串中的?。 最后,在tagsNotesLookup表中,有一个noteUniquelID的索引(索引是SQLite性能的又一个关键)。这行代码在每次启动时都调用:
数据库获取 要获取对象,app调用:
这两行代码做了大部分工作: |