Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSManagedObject changes do not trigger objectWillChange

I have a Core Data model with an entity generated into class Task. I am trying to get the Combine publisher objectWillChange from the NSManagedObject to send (automatically, without manual work), but it won't. The task entity has a name attribute.

let task = Task(context: container.viewContext)

let taskSubscription = task.objectWillChange.sink(receiveValue: { _ in
    print("Task changed")
})

task.name = "Foo"              // WILL NOT trigger

If I call send manually, the subscription will work:

task.objectWillChange.send()   // Will trigger

If I replace this with a simple ObservableObject, it will work as expected:

class DummyTask: ObservableObject {
    @Published var name: String?
}
let dummy = DummyTask()
let dummySubscription = dummy.objectWillChange.sink(receiveValue: { _ in
    print("Dummy changed")
})

dummy.name = "Foo"              // Will trigger
dummy.objectWillChange.send()   // Will trigger

Is NSManagedObject bugged? How should I observe the general entity object for changes? How should I get SwiftUI to see them?

This is using Xcode 11.0 and iOS 13.

like image 814
whistler Avatar asked Sep 23 '19 18:09

whistler


2 Answers

I believe it is a bug. There is no point for NSManagedObject to conform to ObservableObject but unable to mark any property as @Published.

While we are waiting Apple to rectify this, I've come across a cleaner solution than @jesseSpencer's suggested one. The logic behind is the same, by adding a objectWillChange.send(), but adding globally into willChangeValue(forKey key: String) instead of adding into individual properties.

override public func willChangeValue(forKey key: String) {  
    super.willChangeValue(forKey: key)  
    self.objectWillChange.send()  
}

Credits: https://forums.developer.apple.com/thread/121897

like image 188
Anthony Avatar answered Nov 08 '22 11:11

Anthony


My guess is that it is a bug. NSManagedObject conformance to ObservableObject was added in beta 5 which also introduced other significant changes, including deprecation of BindableObject (for replacement by ObservableObject).

See the SwiftUI section: https://developer.apple.com/documentation/ios_ipados_release_notes/ios_13_release_notes)

I ran into the same issue and despite NSManagedObject conforming to ObservableObject, it was not emitting notifications for changes. This might have something to do with NSManagedObject properties needing to be wrapped with @NSManaged which cannot be combined with @Published, while the ObservableObject doc states that, by default, an ObservableObject will synthesize objectWillChange publishers for @Published property changes. https://developer.apple.com/documentation/combine/observableobject

I first tried to get around this by bootstrapping a call to objectWillChange.send() in overrides to Key-Value methods in my NSManagedObject subclass, which only resulted in incorrect behavior.

The solution I went with is the simplest and unfortunately maybe the bulkiest if you need to change a lot of codependent properties in your SwiftUI view. But, so far it is working fine for me and maintains use of SwiftUI as intended.


In Swift:

  1. Create an NSManagedObject subclass for your entity.
  2. In that subclass, create setter methods for the properties you wish to change from your SwiftUI views and at the beginning of the method add a call to objectWillChange.send(), which should look something like this:

    func setTitle(_ text: String) {
        objectWillChange.send()
    
        self.title = text
    }
    

I only advise this as a temporary workaround, as it is not ideal and hopefully will be addressed soon.

I will be submitting a bug report in FeedbackAssistant and I recommend to anyone else encountering this issue to do the same, so we can get Apple to take another look at this!

Edit: A warning about @Anthony’s answer: While the suggested approach does work, be aware that it will not work when changing collection type relationships, i.e. adding an object to an array associated with the NSManagedObject.

like image 6
jesseSpencer Avatar answered Nov 08 '22 09:11

jesseSpencer