如何在 Core Data 中存储具有通用属性的 Swift 对象?

How do you store Swift object with generic properties in Core Data?

我有一个应用程序需要存储与参数的 SwiftUI 控件配置相关的数据(例如滑块上下限、步长等)。参数类型可以是 Int 或 Double,因此将此信息封装在这样的结构中是有意义的:

struct ParameterProfile<T: Comparable & SignedNumeric> {
var parameterName: String
var bounds: ClosedRange<T>
var units: String
var controlStep: T
}

我想让这些配置文件可自定义,并且由于用户可能在多个设备上都有该应用程序,所以我想将其存储在核心数据中,以便通过 CloudKit 进行同步。

此时的选项似乎是将上面的转换为 NSManagedObject 或使用 NSManagedObject 提供数据以实例化结构。

首先,使用 NSManagedObject 并在存储到 Core Data 之前序列化整个 class 似乎是使用 Codable 最实用的方法,因此生成的对象将如下所示:

Class ParameterProfile<T: Comparable & SignedNumeric & Codable>: Codable, NSManagedObject {
var name: String
var bounds: ClosedRange<T>
var units: String
var controlStep: T

enum CodingKeys: String, CodingKey {
   // haven’t figured out this bit yet!
    }
}

然而,即使我添加了一个类型约束来指示泛型类型(无论它是什么)必须是 Codable,泛型类型也不能在 Core Data 中表示。

就目前情况而言,看起来我必须创建一个 NSManagedObject 来有效地包装一个结构,该结构具有可选属性来表示有效范围的类型,例如:

class ProfileWrapper: NSManagedObject {
@NSManaged var name: String
// ...
@NSManaged var type: String
@NSManaged var lowerBound: Double
@NSManaged var upperBound: Double

var materialiseProfile: ParameterProfile {
    ParameterProfile(name: name, type: type, lowerBound: lowerBound, upperBound: upperBound)
}

struct ParameterProfile {
    var name: String
    var intRange: ClosedRange<Int>?
    var doubleRange: ClosedRange<Double>?
    
    init(name: String, type: String, lowerBound: Double, upperBound: Double) {
        self.name = name
        switch type {
        case "int":
            intRange = Int(lowerBound)...Int(upperBound)
        case "double":
            doubleRange = lowerBound...upperBound
        default:
            fatalError("Undefined type")
        }
    }
}

}

上面的方法只是用于说明的粗略代码,但它给了你一个想法——基本上它类似于在 Core Data 中存储枚举的方式。存储相对简单的数据似乎有很多代码,我将不得不单独处理 SwiftUI 控件的类型。由于 ObjC 的限制,我怀疑这是必要的,但这是处理问题的最佳方式还是有其他策略

一起使用泛型和 Core Data 并不容易,所以我认为在这种情况下,您只有两个参数类型,Int 和 Double,最简单的解决方案是为每个创建一个 NSManagedObject 子类。它不是最佳解决方案,但它非常简单易用。您当然需要复制您的获取请求等,但另一方面,您不需要对正确的通用类型进行任何复杂的转换。

下面是 Double 变体的样子

class CDParameterProfileDouble: NSManagedObject {
    @NSManaged var parameterName: String
    @NSManaged var lowerBound: Double
    @NSManaged var upperBound: Double
    @NSManaged var units: String
    @NSManaged var controlStep: Double

    func toDomain() -> ParameterProfile<Double> {
        ParameterProfile(parameterName: self.parameterName,
                         bounds: self.lowerBound...self.upperBound,
                         units: self.units,
                         controlStep: self.controlStep)
    }
}

Int

也一样
class CDParameterProfileInt: NSManagedObject {
    @NSManaged var parameterName: String
    @NSManaged var lowerBound: Int
    @NSManaged var upperBound: Int
    @NSManaged var units: String
    @NSManaged var controlStep: Int

    func toDomain() -> ParameterProfile<Int> {
        ParameterProfile(parameterName: self.parameterName,
                         bounds: self.lowerBound...self.upperBound,
                         units: self.units,
                         controlStep: self.controlStep)
    }
}