2

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

Ayush Goel
  • 3,134
  • 27
  • 36
  • I think your problem is not with KVO, it is with ReactiveSwift and ARC, the `<~` infix operator returns a `Disposable`, try to take a reference to this disposable like you did with `internalVariable = property`, this will keep this Disposable alive. – user9335240 Oct 04 '18 at 10:14
  • That didn't work. :( Extra information: if I create the `DynamicProperty` on `internalAvailability` instead of `availability`, everything works smoothly. – Ayush Goel Oct 04 '18 at 10:20
  • I’m not familiar with ReactiveSwift, but is [this](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVODependentKeys.html#//apple_ref/doc/uid/20002179-BAJEAIEE) relevant? – pbasdf Oct 04 '18 at 10:37
  • Have you tried observing the property with normal KVO? That should allow us to figure out if the issue is with the KVO implementation or the ReactiveSwift portion. – jjoelson Oct 04 '18 at 13:47
  • 1
    Yes I tried that, normal KVO also didn't work. – Ayush Goel Oct 04 '18 at 14:07
  • I think @pbasdf might be right. Have you tried implementing `keyPathsForValuesAffectingValueForKey:`? – jjoelson Oct 04 '18 at 15:19
  • Thanks @pbasdf, I initially was trying to work with `keyPathsForValuesAffectingValueForKey:`, but for some reason swift wasn't ready to accept that with a valid `super` call. cc: thanks @jjoelson – Ayush Goel Oct 08 '18 at 17:17

2 Answers2

2

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]
}
Ayush Goel
  • 3,134
  • 27
  • 36
1

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.

malhal
  • 26,330
  • 7
  • 115
  • 133
  • Can you share documentation of `NSManagedObjectsDidChange`, or did you mean `NSManagedObjectContextObjectsDidChange`. Also, if you read the answer carefully, parent-child relationship is not used in the two MOCs. They have no relationship altogether. – Ayush Goel Mar 31 '20 at 12:35
  • I fixed those 2 mistakes, thanks for letting me know – malhal Mar 31 '20 at 12:54