I have an app that needs to store data relating to SwiftUI control configuration for parameters (e.g. Slider upper and lower bounds, step, etc). The parameter type can be either Int or Double and so it would make sense to encapsulate this information in a struct like this:
struct ParameterProfile<T: Comparable & SignedNumeric> {
var parameterName: String
var bounds: ClosedRange<T>
var units: String
var controlStep: T
}
I am wanting to make these profiles customisable and, as the user may have the app on multiple devices, I’d like to store this in Core Data in order to allow synchronisation via CloudKit.
The options at this point seem to be converting the above to a NSManagedObject or using an NSManagedObject to provide the data to instantiate the struct.
In the first instance, using an NSManagedObject and serialising the entire class before storing in Core Data seems to be the most pragmatic way forward using Codable so the resulting object would look like this:
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!
}
}
However, generic types cannot be represented in Core Data even though I have added a type constraint to indicate that the generic type (whatever it is) must be Codable.
As things stand, it looks like I’ll have to create an NSManagedObject that effectively wraps a struct that has optional properties to represent the type of valid range, something like:
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")
}
}
}
}
The above approach is rough code for illustration only but it gives you an idea - basically it’s similar to the way one would store an enumeration in Core Data. It seems a lot of code to store relatively simple data and I’ll have to handle the type for the SwiftUI control separately. I suspect this is necessary due to the limitations of ObjC, but is this the best way of handling the issue or are there other strategies
CodePudding user response:
Using generics and Core Data together is not easy so I think that in this case where you have only two parameter type, Int and Double, that the easiest solution is to create an NSManagedObject subclass for each. It's not the most optimal solution but it is quite straightforward and easy to work with. You will of course need to duplicate your fetch request etc but on the other hand you won't need to do any complicated casting to the correct generic type.
Here is how the Double variant could look
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)
}
}
and the same for 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)
}
}
