Core Data作为一个OS X和iOS中自带的数据存储框架,很早就存在了。因其存在一些缺点使得很多人放弃使用而采用其它方案。但苹果依然在每个iOS版本中不断对其改进,例如在iOS 8中加入了BetchUpdate,解决了之前一直令人诟病的批量更新问题,使得Core Data更为强大。
0. Core Data Stack
先说说Core Data Stack吧,主要有以下个类组成:
- NSManagedObjectModel
- NSPersistentStore
- NSPersistentStoreCoordinator
- NSManagedObjectContext
NSManagedObjectModel:代表了数据模型,也就是Xcode中创建的.xcdatamodel文件中所表诉的信息。
NSPersistentStore:是数据存放的地方,可以是SQLite、XML(仅OS X)、二进制文件、或仅内存中。
NSPersistentStoreCoordinator:是协调者,用来在NSManagedObjectContext和NSPersistentStore之间建立连接。
NSManagedObjectContext:是应用程序唯一需要访问的类,其中提供了对数据操作的一系列接口。
另外Core Data中最常用的类就是NSFetchRequest和NSManagedObject。
NSFetchRequest用来执行查询,NSManagedObject就是模型对象。
废话不多说,下面来分条介绍Core Data具有哪些特性。
1. 图形用户界面
Xcode为Core Data提供的图形界面是其方便使用的原因之一,使用图形界面可以做以下事情:
- 创建实体极其属性
- 创建关联
- 查看对象图
- 设置二进制数据分离存储
- 完成常见的数据迁移操作
等等……
2. Fetch Template
对于一些固定的Fetch操作,可以用Xcode图形化的创建Fetch Template,来减少代码的编写。
通过FetchTemplate创建FetchRequest
1 | let fetchRequest = managedObjectModel.fetchRequestTemplateForName("templateName") |
3. Fetch Result Type
Fetch数据的结果可以很方便的按需求选择类型,可以以一下四种类型呈现:
- NSManagedObjectResultType:直接返回NSManagedObject对象
- NSCountResultType:返回总数,出于性能考虑,在只需要总数不需要具体对象数据时,用这个类型。
- NSDictionaryResultType:将数据用字典类型返回。
- NSManagedObjectIDResultType:返回所有NSManagedObject对象的唯一标识符
4. NSPredicate
NSPredicate和NSFetchRequest配合可以实现各种各样需求的查询,非常强大。
1 | let dogFetch = NSFetchRequest(entityName: "Dog") |
以上代码为查询name为dogName变量值的Dog对象
出于性能考虑,有多个条件时,应该把最简单的条件放到最前,例如:(salary > 5000000) AND (lastName LIKE 'Quincey')
的性能要比(lastName LIKE 'Quincey') AND (salary > 5000000)
好。
5. Data Validation
Core Data的大部分类型都能通过图形界面方便的设置Validation
比如上图,图中的属性是一个Double类型,可以用面板为它设置最大值最小值。当保存的值不在这个范围内时,会抛出一个异常。
也可以在NSManagedObject的子类中实现validate<属性名>:
方法来自定义Validation
例如:
1 | func validateAge(value: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws { |
也可以实现validateForInsert:
、validateForUpdate:
、validateForDelete:
方法来在,数据插入、更新、删除等操作时进行Validation,这里就不细讲了。
6. NSFetchedResultController
因为Core Data中的数据大部分都会由UITableView来展示,苹果工程师开发了NSFetchedResultController这个组件来使得写很少的代码就能让数据和UITableView绑定,并且保证了良好的性能。
创建NSFetchedResultController:
1 | func initializeFetchedResultsController() { |
和UITableView绑定:
1 | func configureCell(cell: UITableViewCell, indexPath: NSIndexPath) { |
UITableView监听数据变化:
1 | func controllerWillChangeContent(controller: NSFetchedResultsController) { |
7. 异步Fetch
有时Fetch数据的时间会很长,为了避免其阻塞UI,可以使用异步Fetch。这是iOS 8新增的API——NSAsynchronousFetchRequest。不要被它的名字误导了,它并不继承于NSFetchRequest,而是继承于NSPersistentStoreRequest。
1 | fetchRequest = NSFetchRequest(entityName: "Employee") |
如上代码所示,通过NSAsynchronousFetchRequest可以异步的执行一个NSFetchRequest,并在执行结束后回调闭包。
8. Auto Migration
Core Data对一些常见的,不是特别复杂的结构变动支持自动迁移。
一些比较复杂的结构变动也可以在Xcode图形界面中去编辑迁移规则,这样也可以不写代码实现数据迁移。
对于特别复杂的,就自己编写代码来手工迁移吧。
数据迁移部分将来会单独研究。
9. NSUndoManager
Core Data支持用NSUndoManager来回退操作。
10. iCloud支持
Core Data支持方便的将全部或部分数据同步到iCloud中。如果APP中有这样的需求,那么所有本地数据存储方案中,Core Data肯定是首选。
11. Batch Update
有时,我们希望对所有对象的某一个属性值进行改变,也就是批量更新。如果先把这些对象从Core Data中全部读取出来,遍历改变他们的值,然后保存更改,这种操作费时又费性能。
iOS 8后引进了Batch Update,这种方式可以批量更新Core Data中的对象而不需要把它们读入内存。
Batch Update不需要经过NSManagedObjectContext,而是直接操作NSPersistenceStore。
1 | let batchUpdate = NSBatchUpdateRequest(entityName: "Employee") |
如上面代码,为Employee这个实体创建一个NSBatchUpdateRequest对象,通过propertiesToUpdate来设置要改变的属性名和属性值。
通过设置affectedStores来决定要在哪些NSPersistenceStore上生效。
12. Batch Delete
NSBatchDeleteRequest是iOS 9新增的API,用法和NSBatchUpdateRequest差不多,它会直接从PersistenceStore中删除数据,省去先载入到内存,删除,再写回Store的过程。
13. NSExpression
NSExpression也可以和NSFetchRequest配合使用,使用NSExpression内置的一些函数,例如求和“sum:”,求平均数“average:”等等
可以在查询数据的时候带来很好的性能
1 | let fetchRequest = NSFetchRequest(entityName: "Venue") |
14. Instruments
Xcode的Instruments中带有专门调试Core Data的工具,帮助排查使用Core Data遇到的问题。
15. Private Queue Context
当执行一些耗时较长的操作时,例如插入、更新大量数据,用主队列Context会阻塞UI,于是需要将这些操作放到非主线程去做。而Core Data的Context执行的操作都不是线程安全的,如果用GCD这种去将操作放在后台执行会出现不可预期的问题。
Core Data为解决这个问题提供了Private Queue Context,也就是后台Context,可以让后台Context去执行这些操作,从PrivateQueue这个名字上也能看出,这个Queue只有创建它的Context可以访问,Context执行的操作都会限制在这一个Queue上,避免线程安全问题。
1 |
|
看上面代码,通过给concurrencyType传入.PrivateQueueConcurrencyType即可创建一个PrivateQueueContext,把MainQueueContext的persistentStoreCoordinator赋给它,即可让PrivateQueueContext也可以访问MainQueueContext下的持久化存储区。
在performBlock的末尾部分,可以向主队列追加一些操作来更新UI
1 | ... |
16. Child Context
Core Data中的每一个NSManagedObjectContext都有一个父存储区(Parent Store),Context以这个父存储区作为持久化存储区(PersistenceStore)与之交互。
最上层Context的ParentStore就是NSPersistentStoreCoordinator。
每个子Context就以它的父Context为PersistenceStore。
可以为主Context创建一些Child Context去做一些临时的操作,操作执行完以后会保存到主Context,当主Context执行了保存时才会把数据真正保存到存储区。
还有一种用法是可以在程序中创建两个Context,一个主队列Context,一个PrivateQueueContext,然后令主队列Context作为PrivateQueueContext的子Context,这样主队列Context的操作会先保存到PrivateQueueContext中,再由PrivateQueueContext在后台慢慢的保存到PersistenceStore中,来保证界面的性能良好。
关于Child Context本文先不做详细研究。
17. Performance
Core Data的性能方面苹果是花了不少功夫的,比如说采用了faulting技术。除了iOS 7下批量更新的问题,iOS 8以上的Core Data性能应该是不错的。在Core Data的使用中,有很多优化点可以保证良好的性能,本文暂先不做介绍了。