Search code examples
ioscore-datakey-value-observingreactive-cocoareactive-swift

KVO not working for custom property of NSManagedObject


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..


Solution

  • 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]
    }