Core Data 是什么样子的

Core Data 是 iOS3.0 时引入的一个数据持久化的框架。与 sqlite 对比最大的优点莫过于支持对象的存储,苹果的官方文档说其简化了数据库的操作,使用 Core Data 确实可以大量减少代码中的 SQL 语句。

可是现状,大家对于持久化的选择方案仍多数是 FMDB。笔者猜测,最大的原因可能就是性能。

各种持久化方案性能比较

Core Data 是什么

Core Data 是一个模型层的技术,帮助开发者建立代表程序状态的模型层。同时也是一种持久化技术,它能将模型对象的状态持久化到磁盘。它是完全独立于 UI 层级的框架,是作为模型层框架被设计出来的。

Core Data 不是一个 O/RM,但它比 O/RM 能做的更多。它也不是一个 SQL wrapper。它默认使用 SQL,但它是一种更高级的抽象概念。

堆栈

Core Data 有相当多可用的组件。当所有的组件都捆绑到一起的时候,我们把它称作 Core Data 堆栈,这个堆栈有两个主要部分。

一部分是关于对象图管理,这正是你需要很好掌握的那一部分,并且知道怎么使用。 另一部分是关于持久化,比如,保存你模型对象的状态,然后再恢复模型对象的状态。

堆栈结构如下

Core Data 堆栈

NSPersistentStoreCoordinator 是一个位于本地存储文件与缓存层(NSManagedObjectContext)之间的一个持久化层,它是真实操作数据库本地文件。

NSManagedObjectContext 是一个被管理数据的上下文,它实际上是对所有数据库操作的一个缓存层,把所有的操作都先缓存起来避免大量磁盘 IO 造成不流畅,在操作完数据库后调用其 save 方法,就可以把数据库操作提交给持久化层(NSPersistentStoreCoordinator),由持久化层一次性写入数据库文件。

NSManagedObject 是被管理的数据记录,对应数据库的一个表。

另外,Core Data 可以将多个 stores 附属于同一个持久化存储协调器,并且除了存储 SQL 格式外,还有很多存储类型可供选择。 最常见的方案如下

多个 stores 持久化

实际使用

下面是笔者定义的一个 Event 表的元素组成

数据表元素

定义数据模型

1
2
3
4
5
6
7
8
9
class MXWEventModel: NSObject {
    var id: Int64
    var time: Date
    var title: String
    var detail: String
    var addr: String
    
    init(id: Int64, title: String, detail: String, addr: String, time: Date){}
}

新增数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let context = persistentContainer.viewContext

public func add(model: AnyObject) {
	let eventModel: MXWEventModel = model as! MXWEventModel
	let entity = NSEntityDescription.entity(forEntityName: @"MXWEvent", in: context!)
	let obj = NSManagedObject(entity: entity!, insertInto: context)
	obj.setValue(eventModel.id, forKey: "id")
	obj.setValue(eventModel.title, forKey: "title")
	obj.setValue(eventModel.detail, forKey: "detail")
	obj.setValue(eventModel.time, forKey: "time")
	obj.setValue(eventModel.addr, forKey: "addr")
	do {
		try context?.save()
	} catch {
		print(error)
	}
}

删除数据

1
2
3
4
5
6
7
8
9
10
public func delete(id: Int64) {
	let request = NSFetchRequest<NSFetchRequestResult>(entityName: @"MXWEvent")
	request.predicate = NSPredicate(format: "id==\(id)")
	let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
	do {
		try context?.execute(deleteRequest)
	} catch {
		print(error)
	}
}

修改数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public func update(id: Int64, model: AnyObject) {
	let eventModel: MXWEventModel = model as! MXWEventModel
	let request = NSFetchRequest<NSFetchRequestResult>(entityName: @"MXWEvent")
	request.predicate = NSPredicate(format: "id==\(id)")
	do {
		let eventObj = try context?.fetch(request)
		let updateObj = eventObj?.first as! NSManagedObject
		updateObj.setValue(eventModel.title, forKey: "title")
		updateObj.setValue(eventModel.detail, forKey: "detail")
		updateObj.setValue(eventModel.time, forKey: "time")
		updateObj.setValue(eventModel.addr, forKey: "addr")
		do {
			try context?.save()
		} catch {
			print(error)
		}
	} catch {
		print(error)
	}
}

查找数据

1
2
3
4
5
6
7
8
9
10
11
12
13
public func fetch(id: Int64) -> AnyObject? {
	let request = NSFetchRequest<NSFetchRequestResult>(entityName: @"MXWEvent")
	request.predicate = NSPredicate(format: "id==\(id)")
	do {
		let r = try context?.fetch(request)
		for data in r as! [NSManagedObject] {
			let m = MXWEventModel(id: data.value(forKey: "id") as! Int64, title: data.value(forKey: "title") as! String, detail: data.value(forKey: "detail") as! String, addr: data.value(forKey: "addr") as! String, time: data.value(forKey: "time") as! Date)
			return m
		}
	} catch {
		print(error)
	}
}

高级功能

数据迁移

在功能迭代过程中,难免会遇到要修改 .xcdatamodeld 文件。例如,新增或删除一个实体、增加或删除一个原有实体的属性等。如果开发者没有设置数据迁移,那更新后原有的数据将会被清空,所以此时需要进行数据的迁移操作。

Core Data 可以设置轻量级的数据迁移,系统会自动分析差异,进行映射,这种方式只适用于简单的增删实体或是增删属性等操作。除此之外还有一种相当复杂的自定义数据迁移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "Demo")
    container.loadPersistentStores(completionHandler: { storeDescription, error in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
        // 设置数据迁移(shouldMigrateStoreAutomatically 默认值为 true)
        if let sDescription: NSPersistentStoreDescription = storeDescription as NSPersistentStoreDescription {
            sDescription.shouldMigrateStoreAutomatically = true
            sDescription.shouldInferMappingModelAutomatically = false
        }
    })
    return container
}()

思考

对于 Core Data,Apple 官方很久之前就已经推出,但是并不受开发者青睐。笔者在这段时间的学习过程中也在思考这个问题。

下面是笔者学习中遇到的注意点:

  1. Core Data 中没有自增数据类型。因为 Core Data 不能使用数据库思维去使用,所以也就很好解释了。
  2. 设置 context 的 merge 策略,减少数据迁移的麻烦。
  3. 最好使用多线程,可以进一步提升性能。

最后,文章开头给出了性能比较。但是,笔者认为,在客户端并没有很大量的数据写入,只要开发者在使用过程中稍作注意,性能应该不是否决 Core Data 技术方案的理由。反倒,Core Data 对 iCloud 很好的支持,以及数据迁移备份,这些都可以很容易实现。笔者认为,完全可以考虑使用它做客户端的数据持久化方案。

参考文档

《Core Data Programming Guide》