核心数据和Swift:批量删除

核心数据是我非常喜欢使用的框架。 尽管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埃尔卡皮坦的释放。 与其表亲NSBatchUpdateRequestNSBatchDeleteRequest实例直接在一个或多个持久性存储上运行。

不幸的是,这意味着批量删除遭受与批量更新相同的限制。 因为批量删除请求直接影响持久性存储,所以受管理对象上下文不知道批量删除请求的后果。 这也意味着,当托管对象的基础数据由于批量删除请求而发生更改时,将不执行任何验证,也不会发布任何通知。 尽管有这些限制,但关系数据的删除规则仍由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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值