iosdev

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.