KVO not working for custom property of NSManagedObject

859 views Asked by At

I have a subclass of NSManagedObject Folder with a state of Availability

@objc enum Availability: Int16 {
  case unknown
  case available
  case unavailable
}

Folder has to do extra stuff (like delete related files) whenever it's availability changes. So I have

  1. internalAvailability saved in core data
  2. Computed property availability using above property

`

extension Folder {
  @NSManaged private var internalAvailability: Availability
}

extension Folder {
  private func deleteFiles(...) {
  ...
  }

  @objc dynamic public var availability: Availability {
    get {
      return internalAvailability
    }
    set {
      willChangeValue(forKey: "availability")
      deleteFiles()
      internalAvailability = newValue
      didChangeValue(forKey: "availability")
    }
  }
}

Using Reactive, I want to change navigation item's title based on availability but the signal is never called after once!

```

let property = DynamicProperty<NSNumber>(object: folder, keyPath: "availability")
internalVariable = property // To have a reference of property

navigationItem.reactive.title <~ property.map { (stateNumber) -> String in
  guard let a = Availability(rawValue: stateNumber.int16Value) else {
      assertionFailure()
      return ""
  }
  let prefix = a == .available ? "" : "(Nope) "
  return "\(prefix)\(folder.name)"
}

I have explicitly added KVO compliance to the property in hopes that this starts working, but alas no results.

Edit: if I create the DynamicProperty on internalAvailability instead of availability, everything works smoothly..

2

There are 2 answers

0
Ayush Goel On BEST ANSWER

Adding as an answer since it became a learning exercise. Hopefully someone else too would be benefitted.

The app uses a multiple managedObjectContext(moc) architecture. 1 private moc to make changes and 1 main thread moc that synchronises itself using mergeChanges.

In above code, navigationItem is using the folder instance kept with main-moc. The DynamicProperty is listening to KVO changes on this main-moc's folder instance. Let's call this main-folder. When I make changes, I modify the folder instance we have on private-moc. Let's call it private-folder.

On modifying private-folder and calling save on private-moc, a notification of name NSManagedObjectContextDidSave is broadcasted. main-moc synchronizes itself using mergeChanges.

mergeChanges changes main-folder, but notice that it would never call the computed-property-setter availability. It directly changes internalAvailability.

And thus, no KVO notifications are posted of our computed property.

TL;DR When doing KVO on a NSManagedObject subclass, use a stored property instead of computed one. In case you have a multi-moc (managed object context) scenario and use mergeChanges to synchronise, setter for your computed property is not called when synchronising.

Edit (Solution): add method of the pattern keyPathsForValuesAffecting<KeyName> KVO relevant documentation

@objc class func keyPathsForValuesAffectingAvailability() -> Set<NSObject> {
  return [#keyPath(Folder.internalAvailability) as NSObject]
}
2
malhal On

When using Core Data we use the NSManagedObjectContextObjectsDidChange notification instead of KVO. This brings many advantages including coalescing of change events and undo support. If we need to know what attributes changed on an object we can examine changedValuesForCurrentEvent which even includes transient attributes that have a matching keyPathsForValuesAffecting.... These advantages likely outweigh those from a KVO binding framework aka reactive.