Last active
March 4, 2017 09:05
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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