mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-18 06:57:15 +01:00
WIP: prototype for ManagedObjectProtocol
This commit is contained in:
166
Sources/Setup/Dynamic Models/Attribute.swift
Normal file
166
Sources/Setup/Dynamic Models/Attribute.swift
Normal file
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// Attribute.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
|
||||
|
||||
|
||||
// MARK: Operators
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
postfix operator *
|
||||
|
||||
|
||||
// MARK: - AttributeContainer
|
||||
|
||||
public enum AttributeContainer<O: ManagedObjectProtocol> {
|
||||
|
||||
// MARK: - Required
|
||||
|
||||
public final class Required<V: ImportableAttributeType>: AttributeProtocol {
|
||||
|
||||
public static func .= (_ attribute: AttributeContainer<O>.Required<V>, _ value: V) {
|
||||
|
||||
attribute.value = value
|
||||
}
|
||||
|
||||
public static postfix func * (_ attribute: AttributeContainer<O>.Required<V>) -> V {
|
||||
|
||||
return attribute.value
|
||||
}
|
||||
|
||||
public let keyPath: String
|
||||
|
||||
public init(_ keyPath: String, `default`: V = V.cs_emptyValue()) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.defaultValue = `default`.cs_toImportableNativeType()
|
||||
}
|
||||
|
||||
public var value: V {
|
||||
|
||||
get {
|
||||
|
||||
let object = self.accessRawObject()
|
||||
let key = self.keyPath
|
||||
let value = object.value(forKey: key)! as! V.ImportableNativeType
|
||||
return V.cs_fromImportableNativeType(value)!
|
||||
}
|
||||
set {
|
||||
|
||||
let object = self.accessRawObject()
|
||||
let key = self.keyPath
|
||||
object.setValue(newValue.cs_toImportableNativeType(), forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var attributeType: NSAttributeType {
|
||||
|
||||
return V.cs_rawAttributeType
|
||||
}
|
||||
|
||||
internal let defaultValue: Any?
|
||||
internal let isOptional = false
|
||||
|
||||
internal var accessRawObject: () -> NSManagedObject = {
|
||||
|
||||
fatalError("\(O.self) property values should not be accessed")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Optional
|
||||
|
||||
public final class Optional<V: ImportableAttributeType>: AttributeProtocol {
|
||||
|
||||
public static func .= (_ attribute: AttributeContainer<O>.Optional<V>, _ value: V?) {
|
||||
|
||||
attribute.value = value
|
||||
}
|
||||
|
||||
public static postfix func * (_ attribute: AttributeContainer<O>.Optional<V>) -> V? {
|
||||
|
||||
return attribute.value
|
||||
}
|
||||
|
||||
public let keyPath: String
|
||||
|
||||
public init(_ keyPath: String, `default`: V? = nil) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.defaultValue = `default`?.cs_toImportableNativeType()
|
||||
}
|
||||
|
||||
public var value: V? {
|
||||
|
||||
get {
|
||||
|
||||
let object = self.accessRawObject()
|
||||
let key = self.keyPath
|
||||
guard let value = object.value(forKey: key) as! V.ImportableNativeType? else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return V.cs_fromImportableNativeType(value)
|
||||
}
|
||||
set {
|
||||
|
||||
let object = self.accessRawObject()
|
||||
let key = self.keyPath
|
||||
object.setValue(newValue?.cs_toImportableNativeType(), forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var attributeType: NSAttributeType {
|
||||
|
||||
return V.cs_rawAttributeType
|
||||
}
|
||||
|
||||
internal let defaultValue: Any?
|
||||
internal let isOptional = true
|
||||
internal var accessRawObject: () -> NSManagedObject = {
|
||||
|
||||
fatalError("\(O.self) property values should not be accessed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - AttributeProtocol
|
||||
|
||||
internal protocol AttributeProtocol: class {
|
||||
|
||||
static var attributeType: NSAttributeType { get }
|
||||
|
||||
var keyPath: String { get }
|
||||
var isOptional: Bool { get }
|
||||
var defaultValue: Any? { get }
|
||||
var accessRawObject: () -> NSManagedObject { get set }
|
||||
}
|
||||
198
Sources/Setup/Dynamic Models/Entity.swift
Normal file
198
Sources/Setup/Dynamic Models/Entity.swift
Normal file
@@ -0,0 +1,198 @@
|
||||
//
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
68
Sources/Setup/Dynamic Models/ManagedObject.swift
Normal file
68
Sources/Setup/Dynamic Models/ManagedObject.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// ManagedObject.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
|
||||
|
||||
|
||||
// MARK: - ManagedObject
|
||||
|
||||
open class ManagedObject: ManagedObjectProtocol {
|
||||
|
||||
public required init(_ object: NSManagedObject) {
|
||||
|
||||
self.isMeta = false
|
||||
self.rawObject = object
|
||||
self.initializeAttributes(Mirror(reflecting: self), { [unowned object] in object })
|
||||
}
|
||||
|
||||
public required init(asMeta: Void) {
|
||||
|
||||
self.isMeta = true
|
||||
self.rawObject = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let rawObject: NSManagedObject?
|
||||
internal let isMeta: Bool
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Sources/Setup/Dynamic Models/ManagedObjectProtocol.swift
Normal file
119
Sources/Setup/Dynamic Models/ManagedObjectProtocol.swift
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// ManagedObjectProtocol.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 Foundation
|
||||
|
||||
|
||||
// MARK: - ManagedObjectProtocol
|
||||
|
||||
public protocol ManagedObjectProtocol: class {
|
||||
|
||||
static func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self
|
||||
|
||||
static func cs_from(object: NSManagedObject) -> Self
|
||||
}
|
||||
|
||||
public extension ManagedObjectProtocol where Self: ManagedObject {
|
||||
|
||||
public typealias Attribute = AttributeContainer<Self>
|
||||
|
||||
@inline(__always)
|
||||
public static func keyPath<O: ManagedObject, V: ImportableAttributeType>(_ attribute: (Self) -> AttributeContainer<O>.Required<V>) -> String {
|
||||
|
||||
return attribute(self.meta).keyPath
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public static func keyPath<O: ManagedObject, V: ImportableAttributeType>(_ attribute: (Self) -> AttributeContainer<O>.Optional<V>) -> String {
|
||||
|
||||
return attribute(self.meta).keyPath
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public static func `where`(_ condition: (Self) -> Where) -> Where {
|
||||
|
||||
return condition(self.meta)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var meta: Self {
|
||||
|
||||
return self.init(asMeta: ())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSManagedObject
|
||||
|
||||
extension NSManagedObject: ManagedObjectProtocol {
|
||||
|
||||
// MARK: ManagedObjectProtocol
|
||||
|
||||
public static func cs_from(object: NSManagedObject) -> Self {
|
||||
|
||||
@inline(__always)
|
||||
func forceCast<T: NSManagedObject>(_ value: Any) -> T {
|
||||
|
||||
return value as! T
|
||||
}
|
||||
return forceCast(object)
|
||||
}
|
||||
|
||||
public static func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self {
|
||||
|
||||
let object = self.init(entity: entityDescription, insertInto: context)
|
||||
defer {
|
||||
|
||||
context.assign(object, to: store)
|
||||
}
|
||||
return object
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ManagedObject
|
||||
|
||||
extension ManagedObject {
|
||||
|
||||
// MARK: ManagedObjectProtocol
|
||||
|
||||
public static func cs_from(object: NSManagedObject) -> Self {
|
||||
|
||||
return self.init(object)
|
||||
}
|
||||
|
||||
public static func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self {
|
||||
|
||||
let type = NSClassFromString(entityDescription.managedObjectClassName!)! as! NSManagedObject.Type
|
||||
let object = type.init(entity: entityDescription, insertInto: context)
|
||||
defer {
|
||||
|
||||
context.assign(object, to: store)
|
||||
}
|
||||
return self.init(object)
|
||||
}
|
||||
}
|
||||
76
Sources/Setup/Dynamic Models/ObjectModel.swift
Normal file
76
Sources/Setup/Dynamic Models/ObjectModel.swift
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// ObjectModel.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 CoreGraphics
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - ObjectModel
|
||||
|
||||
public final class ObjectModel {
|
||||
|
||||
public let version: String
|
||||
|
||||
public convenience init(version: String, entities: [EntityProtocol]) {
|
||||
|
||||
self.init(version: version, configurationEntities: [DataStack.defaultConfigurationName: entities])
|
||||
}
|
||||
|
||||
public required init(version: String, configurationEntities: [String: [EntityProtocol]]) {
|
||||
|
||||
self.version = version
|
||||
|
||||
var entityConfigurations: [String: Set<NSEntityDescription>] = [:]
|
||||
for (configuration, entities) in configurationEntities {
|
||||
|
||||
entityConfigurations[configuration] = Set(entities.map({ $0.entityDescription }))
|
||||
}
|
||||
let allEntities = Set(entityConfigurations.map({ $0.value }).joined())
|
||||
entityConfigurations[DataStack.defaultConfigurationName] = allEntities
|
||||
|
||||
self.entityConfigurations = entityConfigurations
|
||||
self.entities = allEntities
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let entities: Set<NSEntityDescription>
|
||||
internal let entityConfigurations: [String: Set<NSEntityDescription>]
|
||||
|
||||
internal func createModel() -> NSManagedObjectModel {
|
||||
|
||||
let model = NSManagedObjectModel()
|
||||
model.entities = self.entities.sorted(by: { $0.name! < $1.name! })
|
||||
for (configuration, entities) in self.entityConfigurations {
|
||||
|
||||
model.setEntities(
|
||||
entities.sorted(by: { $0.name! < $1.name! }),
|
||||
forConfigurationName: configuration
|
||||
)
|
||||
}
|
||||
return model
|
||||
}
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
//
|
||||
// Prototype.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Created by John Estropia on 2017/04/03.
|
||||
// Copyright © 2017 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
import ObjectiveC
|
||||
|
||||
|
||||
public protocol ManagedObjectProtocol: class {}
|
||||
|
||||
public protocol EntityProtocol {
|
||||
|
||||
var entityDescription: NSEntityDescription { get }
|
||||
}
|
||||
|
||||
internal protocol AttributeProtocol: class {
|
||||
|
||||
static var attributeType: NSAttributeType { get }
|
||||
var keyPath: String { get }
|
||||
var defaultValue: Any? { get }
|
||||
var accessRawObject: () -> NSManagedObject { get set }
|
||||
}
|
||||
|
||||
open class CoreStoreManagedObject: ManagedObjectProtocol {
|
||||
|
||||
internal let rawObject: NSManagedObject?
|
||||
internal let isMeta: Bool
|
||||
|
||||
public 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Entity<O: CoreStoreManagedObject>: 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.name = entityName
|
||||
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 = false
|
||||
attributeDescription.defaultValue = property.defaultValue
|
||||
attributeDescriptions.append(attributeDescription)
|
||||
}
|
||||
if let baseEntityAttributeDescriptions = mirror.superclassMirror.flatMap(self.initializeAttributes) {
|
||||
|
||||
attributeDescriptions.append(contentsOf: baseEntityAttributeDescriptions)
|
||||
}
|
||||
return attributeDescriptions
|
||||
}
|
||||
}
|
||||
|
||||
public enum AttributeContainer<O: ManagedObjectProtocol> {
|
||||
|
||||
public final class Required<V: ImportableAttributeType>: AttributeProtocol {
|
||||
|
||||
static var attributeType: NSAttributeType { return V.cs_rawAttributeType }
|
||||
|
||||
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
|
||||
let value = object.value(forKey: key)! as! V.ImportableNativeType
|
||||
return V.cs_fromImportableNativeType(value)!
|
||||
}
|
||||
set {
|
||||
|
||||
let object = self.accessRawObject()
|
||||
let key = self.keyPath
|
||||
object.setValue(newValue.cs_toImportableNativeType(), forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
public init(_ keyPath: String, `default`: V = V.cs_emptyValue()) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.defaultValue = `default`
|
||||
}
|
||||
}
|
||||
|
||||
public final class Optional<V: ImportableAttributeType>: AttributeProtocol {
|
||||
|
||||
static var attributeType: NSAttributeType { return V.cs_rawAttributeType }
|
||||
|
||||
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
|
||||
guard let value = object.value(forKey: key) as! V.ImportableNativeType? else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return V.cs_fromImportableNativeType(value)
|
||||
}
|
||||
set {
|
||||
|
||||
let object = self.accessRawObject()
|
||||
let key = self.keyPath
|
||||
object.setValue(newValue?.cs_toImportableNativeType(), forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
public init(_ keyPath: String, `default`: V? = nil) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.defaultValue = `default`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension ManagedObjectProtocol where Self: CoreStoreManagedObject {
|
||||
|
||||
public typealias Attribute = AttributeContainer<Self>
|
||||
|
||||
internal static var meta: Self {
|
||||
|
||||
return self.init(nil)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public static func keyPath<O: CoreStoreManagedObject, V: ImportableAttributeType>(_ attribute: (Self) -> AttributeContainer<O>.Required<V>) -> String {
|
||||
|
||||
return attribute(self.meta).keyPath
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public static func keyPath<O: CoreStoreManagedObject, V: ImportableAttributeType>(_ attribute: (Self) -> AttributeContainer<O>.Optional<V>) -> String {
|
||||
|
||||
return attribute(self.meta).keyPath
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public static func `where`(_ condition: (Self) -> Where) -> Where {
|
||||
|
||||
return condition(self.meta)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//: ### Convenience Operators
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
public func .= <O: ManagedObjectProtocol, V: ImportableAttributeType>(_ attribute: AttributeContainer<O>.Required<V>, _ value: V) {
|
||||
|
||||
attribute.value = value
|
||||
}
|
||||
public func .= <O: ManagedObjectProtocol, V: ImportableAttributeType>(_ attribute: AttributeContainer<O>.Optional<V>, _ value: V?) {
|
||||
|
||||
attribute.value = value
|
||||
}
|
||||
|
||||
postfix operator *
|
||||
public postfix func * <O: ManagedObjectProtocol, V: ImportableAttributeType>(_ attribute: AttributeContainer<O>.Required<V>) -> V {
|
||||
|
||||
return attribute.value
|
||||
}
|
||||
public postfix func * <O: ManagedObjectProtocol, V: ImportableAttributeType>(_ attribute: AttributeContainer<O>.Optional<V>) -> V? {
|
||||
|
||||
return attribute.value
|
||||
}
|
||||
|
||||
public extension AttributeContainer.Required where V: CVarArg {
|
||||
|
||||
public static func == (_ attribute: AttributeContainer<O>.Required<V>, _ value: V) -> Where {
|
||||
|
||||
return Where(attribute.keyPath, isEqualTo: value)
|
||||
}
|
||||
public static func < (_ attribute: AttributeContainer<O>.Required<V>, _ value: V) -> Where {
|
||||
|
||||
return Where("%K < %@", attribute.keyPath, value)
|
||||
}
|
||||
public static func > (_ attribute: AttributeContainer<O>.Required<V>, _ value: V) -> Where {
|
||||
|
||||
return Where("%K > %@", attribute.keyPath, value)
|
||||
}
|
||||
public static func <= (_ attribute: AttributeContainer<O>.Required<V>, _ value: V) -> Where {
|
||||
|
||||
return Where("%K <= %@", attribute.keyPath, value)
|
||||
}
|
||||
public static func >= (_ attribute: AttributeContainer<O>.Required<V>, _ value: V) -> Where {
|
||||
|
||||
return Where("%K >= %@", attribute.keyPath, value)
|
||||
}
|
||||
public static func != (_ attribute: AttributeContainer<O>.Required<V>, _ value: V) -> Where {
|
||||
|
||||
return Where("%K != %@", attribute.keyPath, value)
|
||||
}
|
||||
}
|
||||
public extension AttributeContainer.Optional where V: CVarArg {
|
||||
|
||||
public static func == (_ attribute: AttributeContainer<O>.Optional<V>, _ value: V?) -> Where {
|
||||
|
||||
return Where(attribute.keyPath, isEqualTo: value)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ModelVersion {
|
||||
|
||||
public let version: String
|
||||
internal let entities: Set<NSEntityDescription>
|
||||
internal let entityConfigurations: [String: Set<NSEntityDescription>]
|
||||
|
||||
public convenience init(version: String, entities: [EntityProtocol]) {
|
||||
|
||||
self.init(version: version, configurationEntities: [Into.defaultConfigurationName: entities])
|
||||
}
|
||||
|
||||
public required init(version: String, configurationEntities: [String: [EntityProtocol]]) {
|
||||
|
||||
self.version = version
|
||||
|
||||
var entityConfigurations: [String: Set<NSEntityDescription>] = [:]
|
||||
for (configuration, entities) in configurationEntities {
|
||||
|
||||
entityConfigurations[configuration] = Set(entities.map({ $0.entityDescription }))
|
||||
}
|
||||
let allEntities = Set(entityConfigurations.map({ $0.value }).joined())
|
||||
entityConfigurations[Into.defaultConfigurationName] = allEntities
|
||||
|
||||
self.entityConfigurations = entityConfigurations
|
||||
self.entities = allEntities
|
||||
}
|
||||
|
||||
internal func createModel() -> NSManagedObjectModel {
|
||||
|
||||
let model = NSManagedObjectModel()
|
||||
model.entities = self.entities.sorted(by: { $0.name! < $1.name! })
|
||||
for (configuration, entities) in self.entityConfigurations {
|
||||
|
||||
model.setEntities(
|
||||
entities.sorted(by: { $0.name! < $1.name! }),
|
||||
forConfigurationName: configuration
|
||||
)
|
||||
}
|
||||
return model
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user