Files
CoreStore-JohnEstropia/Sources/Setup/Dynamic Models/Entity.swift
2017-04-05 21:56:19 +09:00

199 lines
6.2 KiB
Swift

//
// Entity.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
import ObjectiveC
// MARK: - EntityProtocol
public protocol EntityProtocol {
var entityDescription: NSEntityDescription { get }
}
// MARK: Entity
public struct Entity<O: ManagedObject>: EntityProtocol {
public let entityDescription: NSEntityDescription
internal var dynamicClass: AnyClass {
return NSClassFromString(self.entityDescription.managedObjectClassName!)!
}
public init(_ entityName: String) {
let dynamicClassName = String(reflecting: O.self)
.appending("__\(entityName)")
.replacingOccurrences(of: ".", with: "_")
.replacingOccurrences(of: "<", with: "_")
.replacingOccurrences(of: ">", with: "_")
// TODO: assign entityName through ModelVersion and
// TODO: set NSEntityDescription.userInfo AnyEntity
let newClass: AnyClass?
if NSClassFromString(dynamicClassName) == nil {
newClass = objc_allocateClassPair(NSManagedObject.self, dynamicClassName, 0)
}
else {
newClass = nil
}
defer {
if let newClass = newClass {
objc_registerClassPair(newClass)
}
}
let entityDescription = NSEntityDescription()
entityDescription.userInfo = [
EntityIdentifier.UserInfoKey.CoreStoreManagedObjectName: String(reflecting: O.self)
]
entityDescription.name = entityName
entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self)
// entityDescription.managedObjectClassName = dynamicClassName // TODO: return to NSManagedObject
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 = property.isOptional
attributeDescription.defaultValue = property.defaultValue
attributeDescriptions.append(attributeDescription)
}
if let baseEntityAttributeDescriptions = mirror.superclassMirror.flatMap(self.initializeAttributes) {
attributeDescriptions.append(contentsOf: baseEntityAttributeDescriptions)
}
return attributeDescriptions
}
}
// MARK: - EntityIdentifier
internal struct EntityIdentifier: Hashable {
// MARK: - Category
internal enum Category: Int {
case coreData
case coreStore
}
// MARK: -
internal let category: Category
internal let interfacedClassName: String
internal init(_ type: NSManagedObject.Type) {
self.category = .coreData
self.interfacedClassName = String(reflecting: type)
}
internal init(_ type: ManagedObject.Type) {
self.category = .coreStore
self.interfacedClassName = String(reflecting: type)
}
internal init(_ type: ManagedObjectProtocol.Type) {
switch type {
case let type as NSManagedObject.Type:
self.init(type)
case let type as ManagedObject.Type:
self.init(type)
default:
CoreStore.abort("\(cs_typeName(ManagedObjectProtocol.self)) is not meant to be implemented by external types.")
}
}
internal init(_ entityDescription: NSEntityDescription) {
if let coreStoreManagedObjectName = entityDescription.userInfo?[EntityIdentifier.UserInfoKey.CoreStoreManagedObjectName] as! String? {
self.category = .coreStore
self.interfacedClassName = coreStoreManagedObjectName
}
else {
self.category = .coreData
self.interfacedClassName = entityDescription.managedObjectClassName!
}
}
// MARK: Equatable
static func == (lhs: EntityIdentifier, rhs: EntityIdentifier) -> Bool {
return lhs.category == rhs.category
&& lhs.interfacedClassName == rhs.interfacedClassName
}
// MARK: Hashable
var hashValue: Int {
return self.category.hashValue
^ self.interfacedClassName.hashValue
}
// MARK: FilePrivate
fileprivate enum UserInfoKey {
fileprivate static let CoreStoreManagedObjectName = "CoreStoreManagedObjectName"
}
}