ManagedModels icon indicating copy to clipboard operation
ManagedModels copied to clipboard

Predicate to NSPredicate conversion fails (not tagged as ObjC)

Open helje5 opened this issue 1 year ago • 6 comments

Follow up to #22 by @HealsCodes:

I've been playing around with Predicates + ManagedModels but always run into a dead-end seemingly because Model properties aren't exposed to the underlying Objective-C runtime so code like this doesn't work:


@Model
final class MyModel: NSManagedObject {
  var enabled: Bool

  convenience init() {
    self.enabled = false
  }
}

// the following works if MyModel is created using the traditional NSManagedObject / @NSManaged means

let p = #Predicate<MyModel> { $0.enabled == true } 
let predicate = NSPredicate(p) // returns nil, because apparently MyModel.enabled isn't bridged

Likewise a simple NSExpression(forKeyPath: \MyModel.enabled) fails with "Foundation/NSObject.swift:132: Fatal error: Could not extract a String from KeyPath \MyModel.enabled"

The same issue breaks interoperability with packages like PredicateKit which relies on being able to convert AnyKeyPath to String using ObjC bridging internals.

helje5 avatar Oct 29 '24 13:10 helje5

So this is currently generated for the property:

@Model
final class MyModel: NSManagedObject {
  @_PersistedProperty
  var enabled: Bool
  {
    set {
        setValue(forKey: "enabled", to: newValue)
    }
    get {
        getValue(forKey: "enabled")
    }
  }
}

And indeed, this is a Swift only property. Looks like just adding @objc will make this work:

@Model
final class MyModel: NSManagedObject {
  
  @objc var enabled2: Bool {
      set {
          setValue(forKey: "enabled", to: newValue)
      }
      get {
          getValue(forKey: "enabled")
      }
  }
}
let p = #Predicate<MyModel> { $0.enabled2 == true }
let predicate = NSPredicate(p) // works

print("Predicate:", predicate)
// Predicate: Optional(enabled2 == 1)

helje5 avatar Oct 29 '24 13:10 helje5

This turns out to be more difficult than expected :-) I.e. this is problematic:

    enum AddressType: Int {
        case home, work
    }
    @Model
    final class Address /*test*/ : NSManagedObject {
      var street     : String
      var type       : AddressType
      var person     : Person

We cannot add @objc to type, because:

Property cannot be marked @objc because its type cannot be represented in Objective-C

Not quite sure how we can figure out whether a node can be represented in ObjC 🤔

helje5 avatar Nov 01 '24 13:11 helje5

I added something to 0.8.14, but I don't think we can fully determine the ObjC convertibility in a macro, as that can't resolve types.

helje5 avatar Nov 01 '24 14:11 helje5

Thank you for putting in all the effort! I'll gladly give 0.8.14 a go once I have time to sit down and work on my own project next.

For me it is already a huge bonus to be able to use key paths this way with types that can be mapped cleanly to Foundation and thus ObjC types.

I had good success manually adding the extra @obj to base types and using that in queries even with PredikateKit 😄

HealsCodes avatar Nov 01 '24 18:11 HealsCodes

Version 0.8.14 introduced a bug where such a model does no longer compile due to presence of Int? and Double?, as well as a Set of Codable structs.

Compilation Error: Property cannot be marked @objcbecause its type cannot be represented in Objective-C

@Model
final class AdvancedStoredAccess: NSManagedObject {
        var token   : String
        var expires : Date
        var distance: Int?
        var avgSpeed: Double?
        var sip     : AccessSIP
        var optionalSip : AccessSIP?
        var codableSet  : Set<AccessSIP>
}

admkopec avatar Feb 04 '25 20:02 admkopec

Added a PR #37 which aims to improve the @objc tagging and fix bugs introduced in 0.8.14

admkopec avatar Feb 12 '25 21:02 admkopec