WWDC20 10017 - Core Data 杂项与准则
本文是对 WWDC 2020 Core Data: Sundries and maxims Session 的翻译。
本 Session 将从三个方面介绍如何优化 Core Data 性能:
下面会以这个地震信息列表 Demo 为例,说明上面的内容:

示例中,从 USGS(美国地质调查局) 获取到 JSON Feed 通过 JSON Parser 解析后,通过 Background Context 存入到持久化存储中,View Context 合并过数据后交给显示层。
在上面的过程中,大量的 Managed Object 在被创建保存之后立即就被废弃了。这正是批量操作的使用场景。
批量操作
批量操作在保持轻量的同时支持插入、更新和删除,但它没有提供结果的通知或回调。一个解决方案是开启持久化历史(Persistent History),这样我们可以得到批量操作的通知。至于回调,我们可以通过解析持久化历史来找出对应的变化。
storeDesc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
接下来会详细解说各个批量操作。
批量插入
iOS 14 在 NSBatchInsertRequest
上新增了一套基于 Block 的批量插入 API:
// NSBatchInsertRequest.h
@available(iOS 13.0, *)
open class NSBatchInsertRequest : NSPersistentStoreRequest {
open var resultType: NSBatchInsertRequestResultType
// iOS 13 旧接口,通过数组批量插入
public convenience init(entityName: String, objects dictionaries: [[String : Any]])
public convenience init(entity: NSEntityDescription, objects dictionaries: [[String : Any]])
// iOS 14 新增,通过 Block 批量插入
@available(iOS 14.0, *)
open var dictionaryHandler: ((inout Dictionary<String, Any>) -> Void)?
open var managedObjectHandler: ((inout NSManagedObject) -> Void)?
public convenience init(entity: NSEntityDescription, dictionaryHandler handler: @escaping (inout Dictionary<String, Any>) -> Void)
public convenience init(entity: NSEntityDescription, managedObjectHandler handler: @escaping (inout NSManagedObject) -> Void)
}
举个🌰,不使用 Batch 逐个插入新对象:
// Earthquakes Sample - Regular Save
for quakeData in quakesBatch {
// 逐个创建 Entity
guard let quake = NSEntityDescription.insertNewObject(forEntityName: "Quake", into: taskContext) as? Quake else { ... }
do {
// 逐个填充数据
try quake.update(with: quakeData)
} catch QuakeError.missingData {
...
taskContext.delete(quake)
}
...
}
do {
try taskContext.save()
} catch { ... }
使用老的数组式 Batch Request 插入对象:
// Earthquakes Sample - Batch Insert
// 构建数组
var quakePropertiesArray = [[String:Any]]()
for quake in quakesBatch {
quakePropertiesArray.append(quake.dictionary)
}
let batchInsert = NSBatchInsertRequest(entityName: "Quake", objects: quakePropertiesArray)
var insertResult : NSBatchInsertResult
do {
insertResult = try taskContext.execute(batchInsert) as! NSBatchInsertResult
...
}
使用 iOS 14 新加的 Block 式 Batch:
//Earthquakes Sample - Batch Insert with a block
var batchInsert = NSBatchInsertRequest(entityName: "Quake", dictionaryHandler: {
(dictionary) in
if (blockCount == batchSize) {
// 返回 true 表示结束
return true
} else {
dictionary = quakesBatch[blockCount]
blockCount += 1
}
})
var insertResult : NSBatchInsertResult
do {
insertResult = try taskContext.execute(batchInsert) as! NSBatchInsertResult
...
}
让我们来看看这三种方式在插入大量数据时性能上的差别:
方式 |
操作耗时 |
内存 |
无 Batch |
62s |
31M |
iOS 13 数组式 Batch |
30s |
25.2 M |
iOS 14 Block 式 Batch |
11s |
24.3 M |
Block 式 Batch 无论在耗时还是在内存峰值上都有最好的表现。非 Batch 的耗时主要花费在合并数据更变 Notification 上了。
自动合并对象
在 Core Data 文件的 Entities 的 右侧 Core Data Inspector 编辑栏 ,将属性加入到 Constraints 后,就在这个属性上建立了一个 Unique 的约束。设置 mergePolicy
可以让 Core Data 自动支持 Model 更新。
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
批量更新
NSBatchUpdateRequest
支持快速批量的更新数据,我们无需再经历查询、更新、保存的老流程。下面的代码将所有 magitute
大于 2.5
的数据的 validate
属性更新为 true
:
// Earthquakes Sample - Batch Update
let updateRequest = NSBatchUpdateRequest(entityName: "Quake")
updateRequest.propertiesToUpdate = ["validated" : true]
updateRequest.predicate = NSPredicate("%K > 2.5", "magnitude")
var updateResult : NSBatchUpdateResult
do {
updateResult = try taskContext.execute(updateRequest) as! NSBatchUpdateResult
...
}
批量删除
NSBatchDeleteRequest
支持:
- 批量删除对象图中的大部分内容
- 遵守对象关系。删除是级联的,关系也会被置空
- 适用于清理过期对象或控制对象的 TTL(存活时间)
下面的代码在后台线程将删除所有 creationDate
在 30 天之前的数据:
```language-swift
// Batch Delete without and with a Fetch Limit
DispatchQueue.global(qos: .background).async {
moc.performAndWait { () -> Void in
do {
let expirationDate = Date.init().addingTimeInterval(-30243600)
let request = NSFetchRequest<Quake>(entityName: "Quake")
WWDC20 10017 - Core Data 杂项与准则
本文是对 WWDC 2020 Core Data: Sundries and maxims Session 的翻译。
本 Session 将从三个方面介绍如何优化 Core Data 性能:
下面会以这个地震信息列表 Demo 为例,说明上面的内容:

示例中,从 USGS(美国地质调查局) 获取到 JSON Feed 通过 JSON Parser 解析后,通过 Background Context 存入到持久化存储中,View Context 合并过数据后交给显示层。
在上面的过程中,大量的 Managed Object 在被创建保存之后立即就被废弃了。这正是批量操作的使用场景。
批量操作
批量操作在保持轻量的同时支持插入、更新和删除,但它没有提供结果的通知或回调。一个解决方案是开启持久化历史(Persistent History),这样我们可以得到批量操作的通知。至于回调,我们可以通过解析持久化历史来找出对应的变化。
storeDesc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
接下来会详细解说各个批量操作。
批量插入
iOS 14 在 NSBatchInsertRequest
上新增了一套基于 Block 的批量插入 API:
// NSBatchInsertRequest.h
@available(iOS 13.0, *)
open class NSBatchInsertRequest : NSPersistentStoreRequest {
open var resultType: NSBatchInsertRequestResultType
// iOS 13 旧接口,通过数组批量插入
public convenience init(entityName: String, objects dictionaries: [[String : Any]])
public convenience init(entity: NSEntityDescription, objects dictionaries: [[String : Any]])
// iOS 14 新增,通过 Block 批量插入
@available(iOS 14.0, *)
open var dictionaryHandler: ((inout Dictionary<String, Any>) -> Void)?
open var managedObjectHandler: ((inout NSManagedObject) -> Void)?
public convenience init(entity: NSEntityDescription, dictionaryHandler handler: @escaping (inout Dictionary<String, Any>) -> Void)
public convenience init(entity: NSEntityDescription, managedObjectHandler handler: @escaping (inout NSManagedObject) -> Void)
}
举个🌰,不使用 Batch 逐个插入新对象:
// Earthquakes Sample - Regular Save
for quakeData in quakesBatch {
// 逐个创建 Entity
guard let quake = NSEntityDescription.insertNewObject(forEntityName: "Quake", into: taskContext) as? Quake else { ... }
do {
// 逐个填充数据
try quake.update(with: quakeData)
} catch QuakeError.missingData {
...
taskContext.delete(quake)
}
...
}
do {
try taskContext.save()
} catch { ... }
使用老的数组式 Batch Request 插入对象:
// Earthquakes Sample - Batch Insert
// 构建数组
var quakePropertiesArray = [[String:Any]]()
for quake in quakesBatch {
quakePropertiesArray.append(quake.dictionary)
}
let batchInsert = NSBatchInsertRequest(entityName: "Quake", objects: quakePropertiesArray)
var insertResult : NSBatchInsertResult
do {
insertResult = try taskContext.execute(batchInsert) as! NSBatchInsertResult
...
}
使用 iOS 14 新加的 Block 式 Batch:
//Earthquakes Sample - Batch Insert with a block
var batchInsert = NSBatchInsertRequest(entityName: "Quake", dictionaryHandler: {
(dictionary) in
if (blockCount == batchSize) {
// 返回 true 表示结束
return true
} else {
dictionary = quakesBatch[blockCount]
blockCount += 1
}
})
var insertResult : NSBatchInsertResult
do {
insertResult = try taskContext.execute(batchInsert) as! NSBatchInsertResult
...
}
让我们来看看这三种方式在插入大量数据时性能上的差别:
方式 |
操作耗时 |
内存 |
无 Batch |
62s |
31M |
iOS 13 数组式 Batch |
30s |
25.2 M |
iOS 14 Block 式 Batch |
11s |
24.3 M |
Block 式 Batch 无论在耗时还是在内存峰值上都有最好的表现。非 Batch 的耗时主要花费在合并数据更变 Notification 上了。
自动合并对象
在 Core Data 文件的 Entities 的 右侧 Core Data Inspector 编辑栏 ,将属性加入到 Constraints 后,就在这个属性上建立了一个 Unique 的约束。设置 mergePolicy
可以让 Core Data 自动支持 Model 更新。
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
批量更新
NSBatchUpdateRequest
支持快速批量的更新数据,我们无需再经历查询、更新、保存的老流程。下面的代码将所有 magitute
大于 2.5
的数据的 validate
属性更新为 true
:
// Earthquakes Sample - Batch Update
let updateRequest = NSBatchUpdateRequest(entityName: "Quake")
updateRequest.propertiesToUpdate = ["validated" : true]
updateRequest.predicate = NSPredicate("%K > 2.5", "magnitude")
var updateResult : NSBatchUpdateResult
do {
updateResult = try taskContext.execute(updateRequest) as! NSBatchUpdateResult
...
}
批量删除
NSBatchDeleteRequest
支持:
- 批量删除对象图中的大部分内容
- 遵守对象关系。删除是级联的,关系也会被置空
- 适用于清理过期对象或控制对象的 TTL(存活时间)
下面的代码在后台线程将删除所有 creationDate
在 30 天之前的数据:
```language-swift
// Batch Delete without and with a Fetch Limit
DispatchQueue.global(qos: .background).async {
moc.performAndWait { () -> Void in
do {
let expirationDate = Date.init().addingTimeInterval(-30243600)
let request = NSFetchRequest<Quake>(entityName: "Quake")
WWDC20 10017 - Core Data 杂项与准则
本文是对 WWDC 2020 Core Data: Sundries and maxims Session 的翻译。
本 Session 将从三个方面介绍如何优化 Core Data 性能:
下面会以这个地震信息列表 Demo 为例,说明上面的内容:

示例中,从 USGS(美国地质调查局) 获取到 JSON Feed 通过 JSON Parser 解析后,通过 Background Context 存入到持久化存储中,View Context 合并过数据后交给显示层。
在上面的过程中,大量的 Managed Object 在被创建保存之后立即就被废弃了。这正是批量操作的使用场景。
批量操作
批量操作在保持轻量的同时支持插入、更新和删除,但它没有提供结果的通知或回调。一个解决方案是开启持久化历史(Persistent History),这样我们可以得到批量操作的通知。至于回调,我们可以通过解析持久化历史来找出对应的变化。
storeDesc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
接下来会详细解说各个批量操作。
批量插入
iOS 14 在 NSBatchInsertRequest
上新增了一套基于 Block 的批量插入 API:
// NSBatchInsertRequest.h
@available(iOS 13.0, *)
open class NSBatchInsertRequest : NSPersistentStoreRequest {
open var resultType: NSBatchInsertRequestResultType
// iOS 13 旧接口,通过数组批量插入
public convenience init(entityName: String, objects dictionaries: [[String : Any]])
public convenience init(entity: NSEntityDescription, objects dictionaries: [[String : Any]])
// iOS 14 新增,通过 Block 批量插入
@available(iOS 14.0, *)
open var dictionaryHandler: ((inout Dictionary<String, Any>) -> Void)?
open var managedObjectHandler: ((inout NSManagedObject) -> Void)?
public convenience init(entity: NSEntityDescription, dictionaryHandler handler: @escaping (inout Dictionary<String, Any>) -> Void)
public convenience init(entity: NSEntityDescription, managedObjectHandler handler: @escaping (inout NSManagedObject) -> Void)
}
举个🌰,不使用 Batch 逐个插入新对象:
// Earthquakes Sample - Regular Save
for quakeData in quakesBatch {
// 逐个创建 Entity
guard let quake = NSEntityDescription.insertNewObject(forEntityName: "Quake", into: taskContext) as? Quake else { ... }
do {
// 逐个填充数据
try quake.update(with: quakeData)
} catch QuakeError.missingData {
...
taskContext.delete(quake)
}
...
}
do {
try taskContext.save()
} catch { ... }
使用老的数组式 Batch Request 插入对象:
// Earthquakes Sample - Batch Insert
// 构建数组
var quakePropertiesArray = [[String:Any]]()
for quake in quakesBatch {
quakePropertiesArray.append(quake.dictionary)
}
let batchInsert = NSBatchInsertRequest(entityName: "Quake", objects: quakePropertiesArray)
var insertResult : NSBatchInsertResult
do {
insertResult = try taskContext.execute(batchInsert) as! NSBatchInsertResult
...
}
使用 iOS 14 新加的 Block 式 Batch:
//Earthquakes Sample - Batch Insert with a block
var batchInsert = NSBatchInsertRequest(entityName: "Quake", dictionaryHandler: {
(dictionary) in
if (blockCount == batchSize) {
// 返回 true 表示结束
return true
} else {
dictionary = quakesBatch[blockCount]
blockCount += 1
}
})
var insertResult : NSBatchInsertResult
do {
insertResult = try taskContext.execute(batchInsert) as! NSBatchInsertResult
...
}
让我们来看看这三种方式在插入大量数据时性能上的差别:
方式 |
操作耗时 |
内存 |
无 Batch |
62s |
31M |
iOS 13 数组式 Batch |
30s |
25.2 M |
iOS 14 Block 式 Batch |
11s |
24.3 M |
Block 式 Batch 无论在耗时还是在内存峰值上都有最好的表现。非 Batch 的耗时主要花费在合并数据更变 Notification 上了。
自动合并对象
在 Core Data 文件的 Entities 的 右侧 Core Data Inspector 编辑栏 ,将属性加入到 Constraints 后,就在这个属性上建立了一个 Unique 的约束。设置 mergePolicy
可以让 Core Data 自动支持 Model 更新。
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
批量更新
NSBatchUpdateRequest
支持快速批量的更新数据,我们无需再经历查询、更新、保存的老流程。下面的代码将所有 magitute
大于 2.5
的数据的 validate
属性更新为 true
:
// Earthquakes Sample - Batch Update
let updateRequest = NSBatchUpdateRequest(entityName: "Quake")
updateRequest.propertiesToUpdate = ["validated" : true]
updateRequest.predicate = NSPredicate("%K > 2.5", "magnitude")
var updateResult : NSBatchUpdateResult
do {
updateResult = try taskContext.execute(updateRequest) as! NSBatchUpdateResult
...
}
批量删除
NSBatchDeleteRequest
支持:
- 批量删除对象图中的大部分内容
- 遵守对象关系。删除是级联的,关系也会被置空
- 适用于清理过期对象或控制对象的 TTL(存活时间)
下面的代码在后台线程将删除所有 creationDate
在 30 天之前的数据:
```language-swift
// Batch Delete without and with a Fetch Limit
DispatchQueue.global(qos: .background).async {
moc.performAndWait { () -> Void in
do {
let expirationDate = Date.init().addingTimeInterval(-30243600)
let request = NSFetchRequest<Quake>(entityName: "Quake")