NSManagedObjectContextObjectsDidChange通知仅在以前访问过修改后的托管对象时发送。为什么?

时间:2018-05-21 18:02:00

标签: ios swift core-data nsnotificationcenter

(有关详细信息,请参阅下面的更新。)

这是一个奇怪的问题。我不能在课堂上发布所有内容(它太大了),所以我将尝试覆盖必要部分:

我有一个带viewDidLoad()方法的视图控制器:

class MyClass {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()        

        // mainViewContext is the viewContext passed by AppDelegate's persistentContainer
        NotificationCenter.default.addObserver(self, selector: #selector(test(notif:)), name: Notification.Name.NSManagedObjectContextObjectsDidChange, object: mainViewContext)
    }
    ...
    @objc func test(notif: Notification) {
        print("I got called")
    }
    ...

所以我做的是添加一个观察者,以便我知道CoreData对象是否已被更改。但是,此通知始终不会发布。当我这样做时(例如在添加观察者后立即在viewDidLoad()中):

self.appDelegate.persistentContainer.performBackgroundTask({ (privateContext) in
    // FileBrowserElement is a managed object I created
    let folder = privateContext.object(with: App.rootFolderObjectID) as! FileBrowserElement
    // Random number so that the name really changes everytime (so that there is something to change when saving)
    folder.name = "newFolderName" + String(Int.random(between: 0, and: 90000))

    do {
        try privateContext.save()
    } catch let error as NSError {
        print("Error: " + error.debugDescription)
    }
})

它正在运作。正在调用test(notif:)。但现在我用它代替它:

self.appDelegate.persistentContainer.performBackgroundTask({ 
(privateContext) in
    // Just another type of managed object I have.
    let folder = privateContext.object(with: App.templateRackObjectID) as! Rack
    folder.name = "newFolderName" + String(Int.random(between: 0, and: 90000))

    do {
        try privateContext.save()
    } catch let error as NSError {
        print("Error: " + error.debugDescription)
    }
})

它已不再适用了。 test(notif:)未被调用。我不知道为什么会这样。我确实只改变了被修改的对象。

(我在AppDelegate中设置了persistentContainer.viewContext.automaticallyMergesChangesFromParent = true

通知是否仅发布在某些类型的托管对象上?有什么我想念的吗?我不知道如何开始调试这个。我一直在努力工作几个小时。这太奇怪了。

任何想法?如果您需要更多信息,请告诉我。正如我所说,我不知道这个问题的重要性。整个班级太大了,不能在这里发帖。

更新 我试图用虚拟项目重新创建这个问题。奇怪的是,它没有发布任何托管对象的通知。我已将其上传到github:https://github.com/d3mueller/NotificationTestProject

也许我在那里做错了,我没看错?

更新2(找到了一些东西。我更新了回购协议) 所以,这非常奇怪。我怀疑结果是真的(我猜)。

似乎只有在您之前加载了要修改访问其属性的托管对象时,才会发布通知。我在开始performBackgroundTask之前添加了此权限:

// As a class attribute, I added `private var rack: Rack!`. Then this is in `viewDidLoad`, right before the background task
   rack = mainViewContext.object(with: App.templateRackObjectID) as! Rack
            _ = rack.name

现在它正在运作。但为什么?这很奇怪。为什么我以前必须访问它?应始终发送通知。有什么想法吗? 如果你想尝试一下,我把它添加到Github上的虚拟项目中。

我真的很感激任何帮助:)

2 个答案:

答案 0 :(得分:0)

今天也偶然发现了这一点。上下文不会保留对象,因此不会“更新”您的对象。我发现将retainsRegisteredObjects设置为true会导致上下文开始发送预期的通知。

答案 1 :(得分:0)

您是否尝试将对象的addObserver设置为nil?

在我看来,您是在与更改内容不同的上下文中观察通知。在此Documentation中,有人说“每次调用此方法时,持久性容器都会创建一个新的NSManagedObjectContext。”因此,当您执行BackgroundTask时,您是在全新的上下文中进行更改,而您只是在监听更改。 mainContext,您根本不应该收到任何通知吗?我很惊讶它第一次起作用。

并且当automaticMergesChangesFromParent时,在这里说:documentation“指示上下文是自动合并保存到其持久性存储协调器还是父上下文的更改。”我没有看到在performBackgroundTask调用中创建的新上下文的任何地方。将是任何其他上下文的子上下文,并且大概也不是mainContext。不过,当privateContext保存后,协调器应该会收到一些更改,但是协调器也不会发送通知。

如果要将背景上下文合并到主上下文中,并且仅从那里接收通知,则可能需要

mergeChanges(fromContextDidSave:)