Custom-typed Core Data attributes
One more step on the path to hide Core Data‘s Objective-C details
One of many great features of mogenerator that I use(d) all the time in Objective-C was its ability to use custom types through userInfo
key-value pairs.
Using attributeValueScalarType
(or attributeValueClassType
for class types) you could have this (translated into Swift):
@NSManaged public var priceTrend: OverUnderTrend
instead of this:
@NSManaged public var priceTrend: Int16
Naturally I continued to use this feature in my Swift projects. Sadly, it can’t work that simply.
CoreData: error: Property 'setPriceTrend:' is a scalar type on class 'Monitor' that does not match its Entity's property's scalar type.
Dynamically generated accessors do not support implicit type coercion.
Cannot generate a setter method for it.
This is how PriceTrend
is declared:
@objc public enum PriceTrend: Int16 {
case down = -1
case flat = 0
case up = 1
}
It’s kind of obvious why the simple one liner attribute declaration won’t work. What needs to be set is the .rawValue
, not the PriceTrend
itself and @NSManaged
can’t know something like that is possible. And who knows is enum
the only custom wrapper around simpler types that I may end up using. I need something more general.
One possible solution…
(1) Create custom protocol that will always extract base type value.
protocol CoreDataRepresentable {
associatedtype CoreDataBaseType
var coredataValue: CoreDataBaseType { get }
init?(coredataValue: CoreDataBaseType)
}
It has an init?
since I also need to create instances of this type. In my enum examples, I can bulk implement this fairly simple:
extension RawRepresentable where Self: CoreDataRepresentable {
var coredataValue: Self.RawValue { return self.rawValue }
init?(coredataValue: CoreDataBaseType) {
self.init(rawValue: coredataValue as! Self.RawValue)
}
}
(2) Now I need to create custom Core Data setter for these custom types, similar to what I did for Optional scalar values
To do this, I need mogenerator to tell me when I am using custom types. At the moment, mogenerator does not have that information exposed to the templates. It’s fairly simple to add that though, thus I modified my custom templates a bit which resulted in this:
public var priceTrend: PriceTrend {
get {
let key = Monitor.Attributes.priceTrend
willAccessValue(forKey: key)
defer { didAccessValue(forKey: key) }
return PriceTrend(coredataValue: primitiveValue(forKey: key) as! PriceTrend.CoreDataBaseType)!
}
set {
let key = Monitor.Attributes.priceTrend
willChangeValue(forKey: key)
defer { didChangeValue(forKey: key) }
setPrimitiveValue(newValue.coredataValue, forKey: key)
}
}
That getter return is kind of ugly, but it will suffice for now.
I do not have much time at the moment to spend on this – any ideas to improve this are welcome.