如何在 Core Data 中为一对多托管对象使用谓词

How to use a predicate for one-to-many managed objects in Core Data

我目前有两个具有一对多关系的 Core Data 托管对象。

目标

extension Goal {

    @nonobjc public class func createFetchRequest() -> NSFetchRequest<Goal> {
        return NSFetchRequest<Goal>(entityName: "Goal")
    }

    @NSManaged public var title: String
    @NSManaged public var date: Date
    @NSManaged public var progress: NSSet?

}

进度

extension Progress {

    @nonobjc public class func createFetchRequest() -> NSFetchRequest<Progress> {
        return NSFetchRequest<Progress>(entityName: "Progress")
    }

    @NSManaged public var date: Date
    @NSManaged public var comment: String?
    @NSManaged public var goal: Goal

}

对于每个目标,您可以有多个 Progress 对象。问题是当我请求以特定 Goal 作为谓词获取 Progress 时,没有返回任何内容。我怀疑我没有正确使用谓词。

这就是我的要求。

  1. 首先,我为 table 视图控制器获取 Goal
var fetchedResultsController: NSFetchedResultsController<Goal>!

if fetchedResultsController == nil {
    let request = Goal.createFetchRequest()
    let sort = NSSortDescriptor(key: "date", ascending: false)
    request.sortDescriptors = [sort]
    request.fetchBatchSize = 20
    
    fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.context, sectionNameKeyPath: "title", cacheName: nil)
    fetchedResultsController.delegate = self
}

fetchedResultsController.fetchRequest.predicate = goalPredicate

do {
    try fetchedResultsController.performFetch()
} catch {
    print("Fetch failed")
}
  1. 并将结果传递给下一个屏幕,Detail视图控制器:
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
    vc.goal = fetchedResultsController.object(at: indexPath)
    navigationController?.pushViewController(vc, animated: true)
}
  1. 最后,我使用 Goal 作为谓词从 Detail 视图控制器获取 Progress
var goal: Goal!
let progressRequest = Progress.createFetchRequest()
progressRequest.predicate = NSPredicate(format: "goal == %@", goal)

if let progress = try? self.context.fetch(progressRequest) {
    print("progress: \(progress)")

    if progress.count > 0 {
        fetchedResult = progress[0]
        print("fetchedResult: \(fetchedResult)")
    }
}

Goal 被正确返回,但我没有得到 Progress 的任何返回。我试过:

progressRequest.predicate = NSPredicate(format: "goal.title == %@", goal.title)

progressRequest.predicate = NSPredicate(format: "ANY goal == %@", goal)

但还是一样的结果。

以下是我建立关系的方式:

// input for Progress from the user
let progress = Progress(context: self.context)
progress.date = Date()
progress.comment = commentTextView.text

// fetch the related Goal
var goalForProgress: Goal!
let goalRequest = Goal.createFetchRequest()
goalRequest.predicate = NSPredicate(format: "title == %@", titleLabel.text!)

if let goal = try? self.context.fetch(goalRequest) {
    if goal.count > 0 {
        goalForProgress = goal[0]
    }
}

// establish the relationship between Goal and Progress
goalForProgress.progress.insert(progress)

// save
if self.context.hasChanges {
    do {
        try self.context.save()
    } catch {
        print("An error occurred while saving: \(error.localizedDescription)")
    }
}

实际上您不需要重新获取数据。可以从关系中获取进度

  • 声明 progress 为原生 Set

    @NSManaged public var progress: Set<Progress>
    
  • DetailViewController中删除viewDidLoad中的获取代码并声明

    var progress: Progress!
    
  • 在第一个视图控制器中过滤进度

    let goal = fetchedResultsController.object(at: indexPath)
    if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController,
       let progress = goal.progress.first(where: {[=12=].goal.title == goal.title}) {
        vc.progress = progress
        navigationController?.pushViewController(vc, animated: true)
    }
    

并考虑以复数形式命名 to-many 关系 (progresses)

我发现这是由于 Core Data Fault 造成的,Core Data 延迟加载数据,除非您明确访问数据,否则不会显示该值。

您可以执行以下操作:

let goal = fetchedResultsController.object(at: indexPath)
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController,
    let progress = goal.progress.first(where: {[=10=].goal.title == goal.title}) {
    vc.goalTitle = goal.title
    vc.date = progress.date
    
    if let comment = progress.comment {
        vc.comment = comment
    }
    
    navigationController?.pushViewController(vc, animated: true)
}

或将returnsObjectsAsFaults设置为false

这里有一个很好的article主题。