Inspiration
Most model-mapping solutions for dynamic schema (Core Data, Realm, JSONModel) involve relying on dynamic bridgeable properties. The problem is this limits the supported property types: we could only use types that are bridgeable to Objective-C. We want a full-Swift solution so we can register custom property types such as URLs, UIImages, etc.
The techniques (tricks? black magic?) used to implement this can be used to improve any kind of dynamic models such as NSUserDefaults, JSON dictionaries, Core Data, Realm objects, etc.
What it does
The presentation will showcase a Playground file that implements a way to ensure the following:
- type-safe attributes (i.e. compile error when values of wrong type are assigned)
- to avoid reliance on dynamic properties which restricts data types to just ObjC-bridgeable types (i.e. support other encodable types such as
URLs,UIImages, etc) - to provide type-aware keypaths (for potential magical type-aware query predicates)
- to compile-time check on as many mistakes as possible (e.g. not allowing subclass keypaths on a baseclass, or forgetting important overrides on a subclass)
But due to time constraints on the hackathon, I focused on Core Data models because with the current implementation we can skip making xcdatamodeld files for models, which is a huge practical application.
How I built it
- Xcode 8.3 beta Playground
- Mainly with clever usage of Swift protocols, generics, and class inheritance magic.
- Does it involve reflection? A little, but not in the way you'd think.
Challenges I ran into
Not enough time to provide a demo app, sorry :(
Accomplishments that I'm proud of
Here's an idea of how the models would look:
class Bird: BaseObject {
let species = DString("species")
}
class Mascot: Bird {
let nickname = DOptionalString("nickname")
let year = DInt("year", default: 2017)
}
Type-safe attributes work well (i.e. compile error when values of wrong type are assigned)
let mascot = Mascot()
mascot.nickname .= "Riko"
// mascot.nickname .= 2017 // COMPILE ERROR!
let nickname: String = .&mascot.nickname// "Riko"
// let number: Int = .&mascot.nickname // COMPILE ERROR!
If you still need to resort to keyPath access, you can do that too. Safely
let keyPath = Mascot.keyPath { $0.nickname } // "name"
// let keyPath = Bird.keyPath { $0.nickname } // COMPILER ERROR (class Bird doesn't know about Mascot's property)
The property types are aware of their parent class, which means we can create type-aware predicates
let predicate: NSPredicate = (Mascot.meta.nickname == "Riko") // OK
// let predicate: NSPredicate = (Bird.meta.nickname == 1) // COMPILER ERROR
What's next
The technique produced can be used in other dynamic models as well, not just Core Data:
- JSON
- NSUserDefaults
- Realm
- Cloud key-value data (e.g. Firebase)
Also, as long as following methods of ValueProtocol
func encode() -> Any
static func decode(_ any: Any) -> Self
is implemented by a type, that type can be supported as well.
Log in or sign up for Devpost to join the conversation.