Skip to content

Instantly share code, notes, and snippets.

@JohnEstropia
Last active March 4, 2017 09:05
Show Gist options
  • Save JohnEstropia/0547bce1a7b2cb076a9e2817eaa4a4f9 to your computer and use it in GitHub Desktop.
Save JohnEstropia/0547bce1a7b2cb076a9e2817eaa4a4f9 to your computer and use it in GitHub Desktop.
try!Swift 2017 Hackathon entry (XCode 8.3 beta playground file): https://devpost.com/software/resource-free-core-data-models
import CoreData
//: # Implementation
protocol ValueProtocol {
static func empty() -> Self
static func attributeType() -> NSAttributeType
func encode() -> Any
static func decode(_ any: Any) -> Self
}
protocol ManagedObjectProtocol: class {}
protocol EntityProtocol {
var entityDescription: NSEntityDescription { get }
}
protocol AttributeProtocol: class {
static var attributeType: NSAttributeType { get }
var keyPath: String { get }
var defaultValue: Any? { get }
var accessRawObject: () -> NSManagedObject { get set }
}
class BaseObject: ManagedObjectProtocol {
let rawObject: NSManagedObject?
let isMeta: Bool
required init(_ object: NSManagedObject?) {
self.isMeta = object == nil
self.rawObject = object
guard let object = object else {
return
}
self.initializeAttributes(Mirror(reflecting: self), { [unowned object] in object })
}
private func initializeAttributes(_ mirror: Mirror, _ accessRawObject: @escaping () -> NSManagedObject) {
_ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, accessRawObject) })
for child in mirror.children {
guard case let property as AttributeProtocol = child.value else {
continue
}
property.accessRawObject = accessRawObject
}
}
}
struct Entity<O: BaseObject>: EntityProtocol {
let entityDescription: NSEntityDescription
init(_ entityName: String) {
let entityDescription = NSEntityDescription()
entityDescription.name = entityName
entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self)
entityDescription.properties = type(of: self).initializeAttributes(Mirror(reflecting: O.meta))
self.entityDescription = entityDescription
}
private static func initializeAttributes(_ mirror: Mirror) -> [NSAttributeDescription] {
var attributeDescriptions: [NSAttributeDescription] = []
for child in mirror.children {
guard case let property as AttributeProtocol = child.value else {
continue
}
let attributeDescription = NSAttributeDescription()
attributeDescription.name = property.keyPath
attributeDescription.attributeType = type(of: property).attributeType
attributeDescription.isOptional = false
attributeDescription.defaultValue = property.defaultValue
attributeDescriptions.append(attributeDescription)
}
if let baseEntityAttributeDescriptions = mirror.superclassMirror.flatMap(self.initializeAttributes) {
attributeDescriptions.append(contentsOf: baseEntityAttributeDescriptions)
}
return attributeDescriptions
}
}
final class Attribute<O: ManagedObjectProtocol, V: ValueProtocol>: AttributeProtocol {
static var attributeType: NSAttributeType { return V.attributeType() }
let keyPath: String
let defaultValue: Any?
var accessRawObject: () -> NSManagedObject = { fatalError("\(O.self) property values should not be accessed") }
var value: V {
get {
let object = self.accessRawObject()
let key = self.keyPath
return V.decode(object.value(forKey: key)!)
}
set {
let object = self.accessRawObject()
let key = self.keyPath
object.setValue(newValue.encode(), forKey: key)
}
}
init(_ keyPath: String, `default`: V = V.empty()) {
self.keyPath = keyPath
self.defaultValue = `default`
}
}
final class OptionalAttribute<O: ManagedObjectProtocol, V: ValueProtocol>: AttributeProtocol {
static var attributeType: NSAttributeType { return V.attributeType() }
let keyPath: String
let defaultValue: Any?
var accessRawObject: () -> NSManagedObject = { fatalError("\(O.self) property values should not be accessed") }
var value: V? {
get {
let object = self.accessRawObject()
let key = self.keyPath
return object.value(forKey: key).flatMap(V.decode)
}
set {
let object = self.accessRawObject()
let key = self.keyPath
if let value = newValue {
object.setValue(value.encode(), forKey: key)
}
else {
object.setValue(nil, forKey: key)
}
}
}
init(_ keyPath: String, `default`: V? = nil) {
self.keyPath = keyPath
self.defaultValue = `default`
}
}
extension ManagedObjectProtocol where Self: BaseObject {
typealias DInt = Attribute<Self, Int>
typealias DString = Attribute<Self, String>
typealias DOptionalInt = OptionalAttribute<Self, Int>
typealias DOptionalString = OptionalAttribute<Self, String>
static var meta: Self {
return self.init(nil)
}
@inline(__always)
static func keyPath<O: BaseObject, V: ValueProtocol>(_ attribute: (Self) -> Attribute<O, V>) -> String {
return attribute(self.meta).keyPath
}
@inline(__always)
static func keyPath<O: BaseObject, V: ValueProtocol>(_ attribute: (Self) -> OptionalAttribute<O, V>) -> String {
return attribute(self.meta).keyPath
}
}
//: ### Convenience Operators
infix operator .= : AssignmentPrecedence
func .= <O: ManagedObjectProtocol, V: ValueProtocol>(_ attribute: Attribute<O, V>, _ value: V) {
attribute.value = value
}
func .= <O: ManagedObjectProtocol, V: ValueProtocol>(_ attribute: OptionalAttribute<O, V>, _ value: V?) {
attribute.value = value
}
prefix operator .&
prefix func .& <O: ManagedObjectProtocol, V: ValueProtocol>(_ attribute: Attribute<O, V>) -> V {
return attribute.value
}
prefix func .& <O: ManagedObjectProtocol, V: ValueProtocol>(_ attribute: OptionalAttribute<O, V>) -> V? {
return attribute.value
}
extension Attribute where V: CVarArg {
static func == (_ attribute: Attribute, _ value: V) -> NSPredicate {
return NSPredicate(format: "%K == %@", argumentArray: [attribute.keyPath, value])
}
static func < (_ attribute: Attribute, _ value: V) -> NSPredicate {
return NSPredicate(format: "%K < %@", argumentArray: [attribute.keyPath, value])
}
static func > (_ attribute: Attribute, _ value: V) -> NSPredicate {
return NSPredicate(format: "%K > %@", argumentArray: [attribute.keyPath, value])
}
static func <= (_ attribute: Attribute, _ value: V) -> NSPredicate {
return NSPredicate(format: "%K <= %@", argumentArray: [attribute.keyPath, value])
}
static func >= (_ attribute: Attribute, _ value: V) -> NSPredicate {
return NSPredicate(format: "%K >= %@", argumentArray: [attribute.keyPath, value])
}
static func != (_ attribute: Attribute, _ value: V) -> NSPredicate {
return NSPredicate(format: "%K != %@", argumentArray: [attribute.keyPath, value])
}
}
extension OptionalAttribute where V: CVarArg {
static func == (_ attribute: OptionalAttribute, _ value: V?) -> NSPredicate {
if let value = value {
return NSPredicate(format: "%K == %@", argumentArray: [attribute.keyPath, value])
}
else {
return NSPredicate(format: "%K == nil", attribute.keyPath)
}
}
}
//: ### Supported Types
extension Int: ValueProtocol {
static func empty() -> Int { return 0 }
static func attributeType() -> NSAttributeType { return .integer64AttributeType }
func encode() -> Any {
return self as NSNumber
}
static func decode(_ any: Any) -> Int {
return (any as! NSNumber).intValue
}
}
extension String: ValueProtocol {
static func empty() -> String { return "" }
static func attributeType() -> NSAttributeType { return .stringAttributeType }
func encode() -> Any {
return self as NSString
}
static func decode(_ any: Any) -> String {
return any as! String
}
}
enum Static {
static let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
}
protocol ModelVersionProtocol: class {
static var version: String { get }
static var entities: [EntityProtocol] { get }
}
extension ModelVersionProtocol {
static func entity<O: BaseObject>(for type: O.Type) -> Entity<O> {
return self.entities.first(where: { $0 is Entity<O> })! as! Entity<O>
}
}
//: # Demo1: Sample Models
class Bird: BaseObject {
let species = DString("species", default: "Swift")
}
class Mascot: Bird {
let nickname = DOptionalString("nickname")
let year = DInt("year", default: 2016)
}
//: # Demo2: Keypaths
let k1 = Bird.keyPath({ $0.species })
k1
let k2 = Mascot.keyPath({ $0.species })
k2
let k3 = Mascot.keyPath({ $0.nickname })
k3
// ERROR!
//let k4 = Bird.keyPath({ $0.nickname })
//k4
//: # Demo2: Sample Objects
// Looks ugly, but we can make a utility for this
let entities = [
"Bird": Entity<Bird>("Bird").entityDescription,
"Mascot": Entity<Mascot>("Mascot").entityDescription
]
let rawBird = NSManagedObject(entity: entities["Bird"]!, insertInto: Static.context)
let rawMascot = NSManagedObject(entity: entities["Mascot"]!, insertInto: Static.context)
// Okay, here we go
let bird = Bird(rawBird)
.&bird.species
type(of: .&bird.species)
bird.species .= "Sparrow"
.&bird.species
// ERROR!
//.&bird.nickname
let mascot = Mascot(rawMascot)
.&mascot.species
.&mascot.nickname
mascot.nickname .= "Riko"
.&mascot.nickname
//: # Demo3: Predicates
let p1 = (Bird.meta.species == "Swift")
let p2 = (Mascot.meta.nickname == "Riko")
// ERROR!
//let p3 = (Bird.meta.nickname == "Riko")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment