尽管Core Data在OS X和iOS上已经存在很多年了,但最近才添加的功能是批处理更新。 多年来,开发人员一直在要求此功能,Apple终于找到了一种将其集成到Core Data中的方法。 在本教程中,我将向您展示批处理更新的工作方式以及它们对Core Data框架的意义。
1.问题
核心数据擅长管理对象图。 对于核心数据而言,即使是具有许多实体和关系的复杂对象图也不是什么大问题。 但是,Core Data确实存在一些薄弱环节,因此更新大量记录就是其中之一。
这个问题很容易理解。 每当您更新记录时,Core Data都会将记录加载到内存中,更新记录,然后将更改保存到持久存储中,例如SQLite数据库。
如果Core Data需要更新大量记录,则需要将每条记录加载到内存中,更新记录,并将更改发送到持久性存储。 如果记录数太大,iOS会由于缺乏资源而急救。 即使运行OS X的设备可能有执行请求的资源,它也会很慢并且会占用大量内存。
另一种方法是分批更新记录,但这也需要大量时间和资源。 在iOS 7上,这是iOS开发人员唯一的选择。 自从iOS 8和OS X Yosemite以来,情况已不再如此。
2.解决方案
在iOS 8或更高版本以及OS X Yosemite或更高版本上,可以直接与持久性存储进行对话并告诉您要更改的内容。 这通常涉及更新属性。 苹果称此功能为批量更新。
有很多陷阱需要提防。 Core Data可以为您做很多事情,在使用批处理更新之前,您甚至可能没有意识到。 验证是一个很好的例子。 由于Core Data直接在永久存储(例如SQLite数据库)上执行批量更新,因此Core Data无法对您写入永久存储的数据执行任何验证。 这意味着您要负责确保不将无效数据添加到持久性存储中。
什么时候使用批量更新? Apple建议仅在传统方法过于占用资源或时间的情况下才使用此功能。 如果您需要将数百或数千封电子邮件标记为已读,则批处理更新是iOS 8及更高版本和OS X Yosemite及更高版本的最佳解决方案。
3.它如何运作?
为了说明批处理更新是如何工作的,我建议我们重新访问Done ,它是一个管理任务列表的简单Core Data应用程序。 我们将在导航栏中添加一个按钮,将列表中的每个项目标记为已完成。
步骤1:Projet设置
从GitHub下载或克隆项目,然后在Xcode 7中打开它。在模拟器中运行该应用程序,并添加一些待办事项。
步骤2:创建栏按钮项
打开ViewController.swift和申报财产, checkAllButton
,类型UIBarButtonItem
顶部。
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let ReuseIdentifierToDoCell = "ToDoCell"
@IBOutlet weak var tableView: UITableView!
var managedObjectContext: NSManagedObjectContext!
var checkAllButton: UIBarButtonItem!
...
}
在ViewController
类的viewDidLoad()
方法中初始化长条按钮项,并将其设置为导航项的左长条按钮项。
// Initialize Check All Button
checkAllButton = UIBarButtonItem(title: "Check All", style: .Plain, target: self, action: "checkAll:")
// Configure Navigation Item
navigationItem.leftBarButtonItem = checkAllButton
步骤3:实现checkAll(_:)
方法
checkAll(_:)
方法非常简单,但是需要注意一些警告。 在下面查看其实现。
func checkAll(sender: UIBarButtonItem) {
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: managedObjectContext)
// Initialize Batch Update Request
let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription!)
// Configure Batch Update Request
batchUpdateRequest.resultType = .UpdatedObjectIDsResultType
batchUpdateRequest.propertiesToUpdate = ["done": NSNumber(bool: true)]
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
// Perform Fetch
try self.fetchedResultsController.performFetch()
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
}
创建批处理请求
我们首先为Item实体创建一个NSEntityDescription
实例,然后使用它来初始化NSBatchUpdateRequest
对象。 该NSBatchUpdateRequest
类的子类NSPersistentStoreRequest
。
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: managedObjectContext)
// Initialize Batch Update Request
let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription!)
我们将批处理更新请求的结果类型设置为.UpdatedObjectIDsResultType
,它是NSBatchUpdateRequestResultType
枚举的成员值。 这意味着批处理更新请求的结果将是一个数组,其中包含由批处理更新请求更改的记录的对象ID( NSManagedObjectID
类的实例)。
// Configure Batch Update Request
batchUpdateRequest.resultType = .UpdatedObjectIDsResultType
我们还将填充批处理更新请求的propertiesToUpdate
属性。 在此示例中,我们将propertiesToUpdate
设置为包含一个键"done"
且值为NSNumber(bool: true)
的字典。 这意味着每个Item记录都将被设置为done ,这就是我们要做的。
// Configure Batch Update Request
batchUpdateRequest.propertiesToUpdate = ["done": NSNumber(bool: true)]
执行批量更新请求
即使批处理更新绕过托管对象上下文,也可以通过在NSManagedObjectContext
实例上调用executeRequest(_:)
来执行批处理更新请求。 此方法接受一个参数,即NSPersistentStoreRequest
类的实例。 因为executeRequest(_:)
是一个throwing方法,所以我们在do-catch
语句中执行该方法。
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
更新托管对象的上下文
即使我们将批处理更新请求移交给托管对象上下文,托管对象上下文也不知道由于执行批处理更新请求而导致的更改。 如前所述,它绕过了托管对象的上下文。 这样可以批量更新其功能和速度。 要解决此问题,我们需要做两件事:
- 将由批处理更新更新的受管对象变成错误
- 告诉获取的结果控制器执行获取以更新用户界面
这是我们在do-catch
语句的do
子句中的checkAll(_:)
方法的后几行中do
的do-catch
。 如果批量更新请求成功,则从NSBatchUpdateResult
对象中提取NSManagedObjectID
实例的数组。
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
然后,我们遍历objectIDs
数组,向托管对象上下文查询相应的NSManagedObject
实例,然后通过调用refreshObject(_:mergeChanges:)
将托管对象作为第一个参数传入,从而将其变为故障。 为了迫使被管理对象出现故障,我们将false
作为第二个参数传递。
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
获取更新的记录
最后一步是告诉获取结果控制器执行获取操作以更新用户界面。 如果不成功,则在do-catch
语句的catch
子句中捕获错误。
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
// Perform Fetch
try self.fetchedResultsController.performFetch()
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
尽管对于轻松操作而言,这似乎很麻烦且相当复杂,但请记住,我们绕过了Core Data堆栈。 换句话说,我们需要照顾一些Core Data通常为我们完成的整理工作。
步骤4:建立并执行
生成项目并在模拟器或物理设备上运行应用程序。 单击或点击右侧的栏按钮项,以检查列表中的每个待办事项。
结论
批处理更新是Core Data框架的重要补充。 它不仅可以满足开发人员多年的需求,而且只要您牢记一些基本规则,就可以轻松实施。 在下一个教程中,我们将仔细研究批删除,这是最近才添加的Core Data框架的另一个功能。
翻译自: https://code.tutsplus.com/tutorials/core-data-and-swift-batch-updates--cms-25120