核心数据是我非常喜欢使用的框架。 尽管Core Data并不完美,但很高兴看到Apple继续在该框架上进行投资。 例如,今年苹果公司增加了批量删除记录的功能。 在上一篇文章中 ,我们讨论了批处理更新。 正如您将在本教程中了解的那样,批量删除的基本概念非常相似。
1.问题
如果Core Data应用程序需要删除大量记录,那么它将面临一个问题。 即使无需将记录加载到内存中将其删除,这也正是Core Data的工作原理。 正如我们在上一篇文章中讨论的那样 ,这有很多缺点。 在引入批处理更新之前,没有适当的解决方案来更新大量记录。 在iOS 9和OS X El Capitan之前,这同样适用于批量删除。
2.解决方案
虽然NSBatchUpdateRequest
类是iOS中8和OS X的优胜美地介绍, NSBatchDeleteRequest
是最近才添加的类,旁边的iOS 9和OS X埃尔卡皮坦的释放。 与其表亲NSBatchUpdateRequest
, NSBatchDeleteRequest
实例直接在一个或多个持久性存储上运行。
不幸的是,这意味着批量删除遭受与批量更新相同的限制。 因为批量删除请求直接影响持久性存储,所以受管理对象上下文不知道批量删除请求的后果。 这也意味着,当托管对象的基础数据由于批量删除请求而发生更改时,将不执行任何验证,也不会发布任何通知。 尽管有这些限制,但关系数据的删除规则仍由Core Data应用。
3.它如何运作?
在上一教程中 ,我们添加了一项功能,以将每个待办事项标记为已完成。 让我们重新访问该应用程序,并添加删除标记为已完成的每个待办事项的功能。
步骤1:专案设定
从GitHub下载或克隆项目,然后在Xcode 7中打开它。确保将项目的部署目标设置为iOS 9或更高版本,以确保NSBatchDeleteRequest
类可用。
步骤2:创建栏按钮项
打开ViewController.swift并声明UIBarButtonItem
类型的属性deleteAllButton
。 您可以删除checkAllButton
属性,因为在本教程中我们将不需要它。
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let ReuseIdentifierToDoCell = "ToDoCell"
@IBOutlet weak var tableView: UITableView!
var managedObjectContext: NSManagedObjectContext!
var deleteAllButton: UIBarButtonItem!
...
}
在ViewController
类的viewDidLoad()
方法中初始化长条按钮项,并将其设置为导航项的左长条按钮项。
// Initialize Delete All Button
deleteAllButton = UIBarButtonItem(title: "Delete All", style: .Plain, target: self, action: "deleteAll:")
// Configure Navigation Item
navigationItem.leftBarButtonItem = deleteAllButton
步骤3:实现deleteAll(_:)
方法
使用NSBatchDeleteRequest
类并不难,但是我们需要照顾一些直接在持久性存储上运行所固有的问题。
func deleteAll(sender: UIBarButtonItem) {
// Create Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Item")
// Configure Fetch Request
fetchRequest.predicate = NSPredicate(format: "done == 1")
// Initialize Batch Delete Request
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
// Configure Batch Update Request
batchDeleteRequest.resultType = .ResultTypeCount
do {
// Execute Batch Request
let batchDeleteResult = try managedObjectContext.executeRequest(batchDeleteRequest) as! NSBatchDeleteResult
print("The batch delete request has deleted \(batchDeleteResult.result!) records.")
// Reset Managed Object Context
managedObjectContext.reset()
// Perform Fetch
try self.fetchedResultsController.performFetch()
// Reload Table View
tableView.reloadData()
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
}
创建提取请求
一个NSBatchDeleteRequest
对象初始化与NSFetchRequest
对象。 正是此提取请求确定将从持久性存储中删除哪些记录。 在deleteAll(_:)
,我们为Item实体创建获取请求。 我们设置提取请求的predicate
属性以确保仅删除标记为已完成的项目记录。
// Create Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Item")
// Configure Fetch Request
fetchRequest.predicate = NSPredicate(format: "done == 1")
因为获取请求决定了将删除哪些记录,所以我们可以利用NSFetchRequest
类的所有功能,包括设置记录数量的限制,使用排序描述符以及为获取请求指定偏移量。
创建批处理请求
如前所述,批处理删除请求是使用NSFetchRequest
实例初始化的。 因为NSBatchDeleteRequest
类是NSPersistentStoreRequest
子类,所以我们可以设置请求的resultType
属性以指定我们感兴趣的结果类型。
// Initialize Batch Delete Request
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
// Configure Batch Update Request
batchDeleteRequest.resultType = .ResultTypeCount
NSBatchDeleteRequest
实例的resultType
属性的类型为NSBatchDeleteRequestResultType
NSBatchDeleteRequestResultType
枚举定义了三个成员变量:
-
ResultTypeStatusOnly
:这告诉我们批量删除请求是成功还是失败。 -
ResultTypeObjectIDs
:这为我们提供了一个NSManagedObjectID
实例数组,这些实例与由批处理删除请求删除的记录相对应。
ResultTypeCount
:通过将请求的resultType
属性设置为ResultTypeCount
,可以得到受批量删除请求影响(删除)的记录数。
执行批量更新请求
您可能还记得上一教程中的executeRequest(_:)
是一种抛出方法。 这意味着我们需要将方法调用包装在do-catch
语句中。 executeRequest(_:)
方法返回一个NSPersistentStoreResult
对象。 因为我们正在处理批量删除请求,所以我们将结果NSBatchDeleteResult
转换为NSBatchDeleteResult
对象。 结果将打印到控制台。
do {
// Execute Batch Request
let batchDeleteResult = try managedObjectContext.executeRequest(batchDeleteRequest) as! NSBatchDeleteResult
print("The batch delete request has deleted \(batchDeleteResult.result!) records.")
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
如果要运行该应用程序,并在其中填充一些项目,然后点击Delete All(全部删除)按钮,则不会更新用户界面。 我可以向您保证,批量删除请求确实可以正常工作。 请记住,不会以任何方式通知托管对象上下文批删除请求的后果。 显然,这是我们需要解决的问题。
更新托管对象的上下文
在上一教程中,我们使用了NSBatchUpdateRequest
类。 我们通过刷新受批次更新请求影响的托管对象上下文中的对象来更新托管对象上下文。
对于批处理删除请求,我们不能使用相同的技术,因为某些对象不再由持久性存储中的记录表示。 我们需要采取严厉的措施,如下所示。 我们在托管对象上下文上调用reset()
,这意味着托管对象上下文以干净的状态开始。
do {
// Execute Batch Request
let batchDeleteResult = try managedObjectContext.executeRequest(batchDeleteRequest) as! NSBatchDeleteResult
print("The batch delete request has deleted \(batchDeleteResult.result!) records.")
// Reset Managed Object Context
managedObjectContext.reset()
// Perform Fetch
try self.fetchedResultsController.performFetch()
// Reload Table View
tableView.reloadData()
} catch {
let updateError = error as NSError
print("\(updateError), \(updateError.userInfo)")
}
这也意味着获取的结果控制器需要执行获取以更新其为我们管理的记录。 为了更新用户界面,我们在表视图上调用reloadData()
。
4.删除前保存状态
当您直接与持久性存储进行交互时,请务必小心。 在本系列的前面,我写道,无论何时添加,更新或删除记录,都不必保存托管对象上下文的更改。 该语句仍然成立,但在使用NSPersistentStoreRequest
子类时也会产生后果。
在继续之前,我想为持久性存储添加虚拟数据,以便我们进行一些工作。 这使得可视化我将要解释的内容变得更加容易。 将以下辅助方法添加到ViewController.swift并在viewDidLoad()
调用它。
// MARK: -
// MARK: Helper Methods
private func seedPersistentStore() {
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: managedObjectContext)
for i in 0...15 {
// Initialize Record
let record = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
// Populate Record
record.setValue((i % 3) == 0, forKey: "done")
record.setValue(NSDate(), forKey: "createdAt")
record.setValue("Item \(i + 1)", forKey: "name")
}
do {
// Save Record
try managedObjectContext?.save()
} catch {
let saveError = error as NSError
print("\(saveError), \(saveError.userInfo)")
}
}
在seedPersistentStore()
,我们创建一些记录并将每三个项目标记为完成。 请注意,在此方法的结尾,我们在托管对象上下文上调用save()
以确保将更改推送到持久性存储中。 在viewDidLoad()
,我们为持久性存储添加种子。
override func viewDidLoad() {
super.viewDidLoad()
...
// Seed Persistent Store
seedPersistentStore()
}
运行该应用程序,然后点击全部删除按钮。 标记为完成的记录应删除。 如果您将剩余的一些项目标记为完成,然后再次点击全部删除按钮,将会发生什么。 这些项目也被删除了吗? 你能猜出为什么吗?
批删除请求直接与持久性存储进行交互。 但是,当一个项目标记为已完成时,更改不会立即推送到持久性存储中。 每次用户将项目标记为完成时,我们都不会在托管对象上下文上调用save()
。 我们仅在将应用程序推送到后台以及终止时执行此操作(请参阅AppDelegate.swift )。
解决方案很简单。 要解决此问题,我们需要在执行批量删除请求之前保存托管对象上下文的更改。 deleteAll(_:)
添加到deleteAll(_:)
方法,然后再次运行该应用程序以测试解决方案。
func deleteAll(sender: UIBarButtonItem) {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
let saveError = error as NSError
print("\(saveError), \(saveError.userInfo)")
}
}
...
}
结论
NSPersistentStoreRequest
子类是Core Data框架的一个非常有用的添加,但是我希望很明显,仅在绝对必要时才使用它们。 苹果仅增加了直接在持久性存储上运行的功能,以修补框架的弱点,但建议您谨慎使用它们。
翻译自: https://code.tutsplus.com/tutorials/core-data-and-swift-batch-deletes--cms-25380