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.

Built With

Share this project: