Merge branch 'corestore4_develop' of github.com:JohnEstropia/CoreStore into corestore4_develop

This commit is contained in:
John Rommel Estropia
2017-04-25 22:08:58 +09:00
41 changed files with 1623 additions and 374 deletions

View File

@@ -0,0 +1,287 @@
//
// DynamicSchema+Convenience.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: - DynamicSchema
public extension DynamicSchema {
public func printCoreStoreSchema() -> String {
let model = self.rawModel()
let entitiesByName = model.entitiesByName
var output = "/// Generated by CoreStore on \(DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .short))\n"
var addedInverse: Set<String> = []
for (entityName, entity) in entitiesByName {
let superName: String
if let superEntity = entity.superentity {
superName = superEntity.name!
}
else {
superName = String(describing: CoreStoreObject.self)
}
output.append("class \(entityName): \(superName) {\n")
defer {
output.append("}\n")
}
let attributesByName = entity.attributesByName
if !attributesByName.isEmpty {
output.append(" \n")
for (attributeName, attribute) in attributesByName {
let containerType: String
if attribute.attributeType == .transformableAttributeType {
if attribute.isOptional {
containerType = "Transformable.Optional"
}
else {
containerType = "Transformable.Required"
}
}
else {
if attribute.isOptional {
containerType = "Value.Optional"
}
else {
containerType = "Value.Required"
}
}
let valueType: Any.Type
var defaultString = ""
switch attribute.attributeType {
case .integer16AttributeType:
valueType = Int16.self
if let defaultValue = (attribute.defaultValue as! Int16.ImportableNativeType?).flatMap(Int16.cs_fromImportableNativeType),
defaultValue != Int16.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .integer32AttributeType:
valueType = Int32.self
if let defaultValue = (attribute.defaultValue as! Int32.ImportableNativeType?).flatMap(Int32.cs_fromImportableNativeType),
defaultValue != Int32.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .integer64AttributeType:
valueType = Int64.self
if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType),
defaultValue != Int64.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .decimalAttributeType:
valueType = NSDecimalNumber.self
if let defaultValue = (attribute.defaultValue as! NSDecimalNumber.ImportableNativeType?).flatMap(NSDecimalNumber.cs_fromImportableNativeType),
defaultValue != NSDecimalNumber.cs_emptyValue() {
defaultString = ", default: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
}
case .doubleAttributeType:
valueType = Double.self
if let defaultValue = (attribute.defaultValue as! Double.ImportableNativeType?).flatMap(Double.cs_fromImportableNativeType),
defaultValue != Double.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .floatAttributeType:
valueType = Float.self
if let defaultValue = (attribute.defaultValue as! Float.ImportableNativeType?).flatMap(Float.cs_fromImportableNativeType),
defaultValue != Float.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .stringAttributeType:
valueType = String.self
if let defaultValue = (attribute.defaultValue as! String.ImportableNativeType?).flatMap(String.cs_fromImportableNativeType),
defaultValue != String.cs_emptyValue() {
// TODO: escape strings
defaultString = ", default: \"\(defaultValue)\""
}
case .booleanAttributeType:
valueType = Bool.self
if let defaultValue = (attribute.defaultValue as! Bool.ImportableNativeType?).flatMap(Bool.cs_fromImportableNativeType),
defaultValue != Bool.cs_emptyValue() {
defaultString = ", default: \(defaultValue ? "true" : "false")"
}
case .dateAttributeType:
valueType = Date.self
if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType),
defaultValue != Date.cs_emptyValue() {
defaultString = ", default: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
}
case .binaryDataAttributeType:
valueType = Data.self
if let defaultValue = (attribute.defaultValue as! Data.ImportableNativeType?).flatMap(Data.cs_fromImportableNativeType),
defaultValue != Data.cs_emptyValue() {
let count = defaultValue.count
let bytes = defaultValue.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) in
return (0 ..< (count / MemoryLayout<UInt8>.size))
.map({ "\("0x\(String(pointer[$0], radix: 16, uppercase: false))")" })
}
defaultString = ", default: Data(bytes: [\(bytes.joined(separator: ", "))])"
}
case .transformableAttributeType:
if let attributeValueClassName = attribute.attributeValueClassName {
valueType = NSClassFromString(attributeValueClassName)!
}
else {
valueType = (NSCoding & NSCopying).self
}
if let defaultValue = attribute.defaultValue {
defaultString = ", default: /* \"\(defaultValue)\" */"
}
else if !attribute.isOptional {
defaultString = ", default: /* required */"
}
default:
fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)")
}
let indexedString = attribute.isIndexed ? ", isIndexed: true" : ""
let transientString = attribute.isTransient ? ", isTransient: true" : ""
// TODO: escape strings
let versionHashModifierString = attribute.versionHashModifier.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
// TODO: escape strings
let renamingIdentifierString = attribute.renamingIdentifier.flatMap({ ", renamingIdentifier: \"\($0)\"" }) ?? ""
output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(indexedString)\(defaultString)\(transientString)\(versionHashModifierString)\(renamingIdentifierString))\n")
}
}
let relationshipsByName = entity.relationshipsByName
if !relationshipsByName.isEmpty {
output.append(" \n")
for (relationshipName, relationship) in relationshipsByName {
let containerType: String
var minCountString = ""
var maxCountString = ""
if relationship.isToMany {
let minCount = relationship.minCount
let maxCount = relationship.maxCount
if relationship.isOrdered {
containerType = "Relationship.ToManyOrdered"
}
else {
containerType = "Relationship.ToManyUnordered"
}
if minCount > 0 {
minCountString = ", minCount: \(minCount)"
}
if maxCount > 0 {
maxCountString = ", maxCount: \(maxCount)"
}
}
else {
containerType = "Relationship.ToOne"
}
var inverseString = ""
let relationshipQualifier = "\(entityName).\(relationshipName)"
if !addedInverse.contains(relationshipQualifier),
let inverseRelationship = relationship.inverseRelationship {
inverseString = ", inverse: { $0.\(inverseRelationship.name) }"
addedInverse.insert("\(relationship.destinationEntity!.name!).\(inverseRelationship.name)")
}
var deleteRuleString = ""
if relationship.deleteRule != .nullifyDeleteRule {
switch relationship.deleteRule {
case .cascadeDeleteRule:
deleteRuleString = ", deleteRule: .cascade"
case .denyDeleteRule:
deleteRuleString = ", deleteRule: .deny"
case .nullifyDeleteRule:
deleteRuleString = ", deleteRule: .nullify"
default:
fatalError("Unsupported delete rule \((relationship.deleteRule)) for relationship \"\(relationshipQualifier)\"")
}
}
let versionHashModifierString = relationship.versionHashModifier.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
let renamingIdentifierString = relationship.renamingIdentifier.flatMap({ ", renamingIdentifier: \"\($0)\"" }) ?? ""
output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString)\(versionHashModifierString)\(renamingIdentifierString))\n")
}
}
}
output.append("\n\n\n")
output.append("CoreStoreSchema(\n")
output.append(" modelVersion: \"\(self.modelVersion)\",\n")
output.append(" entities: [\n")
for (entityName, entity) in entitiesByName {
var abstractString = ""
if entity.isAbstract {
abstractString = ", isAbstract: true"
}
var versionHashModifierString = ""
if let versionHashModifier = entity.versionHashModifier {
versionHashModifierString = ", versionHashModifier: \"\(versionHashModifier)\""
}
output.append(" Entity<\(entityName)>(\"\(entityName)\"\(abstractString)\(versionHashModifierString)),\n")
}
output.append(" ],\n")
output.append(" versionLock: \(VersionLock(entityVersionHashesByName: model.entityVersionHashesByName).description.components(separatedBy: "\n").joined(separator: "\n "))\n")
output.append(")\n\n")
return output
}
}

View File

@@ -112,8 +112,7 @@ public extension NSManagedObject {
}
@nonobjc @inline(__always)
@discardableResult
public func setValue(_ value: Any?, forKvcKey KVCKey: KeyPath) -> Any? {
public func setValue(_ value: Any?, forKvcKey KVCKey: KeyPath) {
self.willChangeValue(forKey: KVCKey)
defer {
@@ -121,12 +120,10 @@ public extension NSManagedObject {
self.didChangeValue(forKey: KVCKey)
}
self.setPrimitiveValue(value, forKey: KVCKey)
return value
}
@nonobjc @inline(__always)
@discardableResult
public func setValue<T>(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?) rethrows -> T {
public func setValue<T>(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?) rethrows {
self.willChangeValue(forKey: KVCKey)
defer {
@@ -134,20 +131,6 @@ public extension NSManagedObject {
self.didChangeValue(forKey: KVCKey)
}
self.setPrimitiveValue(try willSetValue(value), forKey: KVCKey)
return value
}
@nonobjc @inline(__always)
@discardableResult
public func setValue<T>(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?, didSetValue: (T) -> T = { $0 }) rethrows -> T {
self.willChangeValue(forKey: KVCKey)
defer {
self.didChangeValue(forKey: KVCKey)
}
self.setPrimitiveValue(try willSetValue(value), forKey: KVCKey)
return didSetValue(value)
}
/**

View File

@@ -0,0 +1,115 @@
//
// UserInfo.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: UserInfo
/**
The `UserInfo` class is provided by several CoreStore types such as `DataStack`, `ListMonitor`, `ObjectMonitor` and transactions to allow external libraries or user apps to store their own custom data.
```
enum Static {
static var myDataKey: Void?
}
CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject
```
- Important: Do not use this class to store thread-sensitive data.
*/
public final class UserInfo {
/**
Allows external libraries to store custom data. App code should rarely have a need for this.
```
enum Static {
static var myDataKey: Void?
}
CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject
```
- Important: Do not use this method to store thread-sensitive data.
- parameter key: the key for custom data. Make sure this is a static pointer that will never be changed.
*/
public subscript(key: UnsafeRawPointer) -> Any? {
get {
self.lock.lock()
defer {
self.lock.unlock()
}
return self.data[key]
}
set {
self.lock.lock()
defer {
self.lock.unlock()
}
self.data[key] = newValue
}
}
/**
Allows external libraries to store custom data in the `DataStack`. App code should rarely have a need for this.
```
enum Static {
static var myDataKey: Void?
}
CoreStore.defaultStack.userInfo[&Static.myDataKey, lazyInit: { MyObject() }] = myObject
```
- Important: Do not use this method to store thread-sensitive data.
- parameter key: the key for custom data. Make sure this is a static pointer that will never be changed.
- parameter lazyInit: a closure to use to lazily-initialize the data
- returns: A custom data identified by `key`
*/
public subscript(key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any {
self.lock.lock()
defer {
self.lock.unlock()
}
if let value = self.data[key] {
return value
}
let value = closure()
self.data[key] = value
return value
}
// MARK: Internal
internal init() {}
// MARK: Private
private var data: [UnsafeRawPointer: Any] = [:]
private let lock = NSRecursiveLock()
}

View File

@@ -45,7 +45,7 @@ public typealias ModelConfiguration = String?
// MARK: - ModelVersion
/**
An `String` that pertains to the name of a versioned *.xcdatamodeld file (without the file extension).
An `String` that pertains to the name of a versioned *.xcdatamodeld file (without the file extension). Model version strings don't necessarily have to be numeric or ordered in any way. The migration sequence will always be decided by (or the lack of) the `MigrationChain`.
*/
public typealias ModelVersion = String

View File

@@ -1,5 +1,5 @@
//
// Attribute+Querying.swift
// CoreStoreObject+Querying.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
@@ -27,30 +27,72 @@ import CoreData
import Foundation
// MARK: - DynamicObject
public extension DynamicObject where Self: CoreStoreObject {
public static func keyPath<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Required<V>) -> String {
return attribute(self.meta).keyPath
}
public static func keyPath<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> String {
return attribute(self.meta).keyPath
}
public static func `where`(_ condition: (Self) -> Where) -> Where {
return condition(self.meta)
}
public static func ascending<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.ascending(attribute(self.meta).keyPath))
}
public static func descending<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.descending(attribute(self.meta).keyPath))
}
}
// MARK: - ValueContainer.Required
public extension ValueContainer.Required {
@inline(__always)
public static func == (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where(attribute.keyPath, isEqualTo: value)
}
@inline(__always)
public static func < (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where("%K < %@", attribute.keyPath, value)
}
@inline(__always)
public static func > (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where("%K > %@", attribute.keyPath, value)
}
@inline(__always)
public static func <= (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where("%K <= %@", attribute.keyPath, value)
}
@inline(__always)
public static func >= (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where("%K >= %@", attribute.keyPath, value)
}
@inline(__always)
public static func != (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return !Where(attribute.keyPath, isEqualTo: value)
@@ -62,11 +104,13 @@ public extension ValueContainer.Required {
public extension ValueContainer.Optional {
@inline(__always)
public static func == (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
return Where(attribute.keyPath, isEqualTo: value)
}
@inline(__always)
public static func != (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
return !Where(attribute.keyPath, isEqualTo: value)

View File

@@ -39,9 +39,9 @@ public extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableObject` methods
- returns: the created `ImportableObject` instance, or `nil` if the import was ignored
*/
public func importObject<T>(
public func importObject<T: DynamicObject & ImportableObject>(
_ into: Into<T>,
source: T.ImportSource) throws -> T? where T: DynamicObject, T: ImportableObject {
source: T.ImportSource) throws -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
@@ -69,9 +69,9 @@ public extension BaseDataTransaction {
- parameter source: the object to import values from
- throws: an `Error` thrown from any of the `ImportableObject` methods
*/
public func importObject<T>(
public func importObject<T: DynamicObject & ImportableObject>(
_ object: T,
source: T.ImportSource) throws where T: DynamicObject, T: ImportableObject {
source: T.ImportSource) throws {
CoreStore.assert(
self.isRunningInAllowedQueue(),
@@ -97,9 +97,9 @@ public extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableObject` methods
- returns: the array of created `ImportableObject` instances
*/
public func importObjects<T, S: Sequence>(
public func importObjects<T: DynamicObject & ImportableObject, S: Sequence>(
_ into: Into<T>,
sourceArray: S) throws -> [T] where T: DynamicObject, T: ImportableObject, S.Iterator.Element == T.ImportSource {
sourceArray: S) throws -> [T] where S.Iterator.Element == T.ImportSource {
CoreStore.assert(
self.isRunningInAllowedQueue(),
@@ -133,9 +133,9 @@ public extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableUniqueObject` methods
- returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored
*/
public func importUniqueObject<T>(
public func importUniqueObject<T: DynamicObject & ImportableUniqueObject>(
_ into: Into<T>,
source: T.ImportSource) throws -> T? where T: DynamicObject, T: ImportableUniqueObject {
source: T.ImportSource) throws -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
@@ -185,10 +185,10 @@ public extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableUniqueObject` methods
- returns: the array of created/updated `ImportableUniqueObject` instances
*/
public func importUniqueObjects<T, S: Sequence>(
public func importUniqueObjects<T: DynamicObject & ImportableUniqueObject, S: Sequence>(
_ into: Into<T>,
sourceArray: S,
preProcess: @escaping (_ mapping: [T.UniqueIDType: T.ImportSource]) throws -> [T.UniqueIDType: T.ImportSource] = { $0 }) throws -> [T] where T: DynamicObject, T: ImportableUniqueObject, S.Iterator.Element == T.ImportSource {
preProcess: @escaping (_ mapping: [T.UniqueIDType: T.ImportSource]) throws -> [T.UniqueIDType: T.ImportSource] = { $0 }) throws -> [T] where S.Iterator.Element == T.ImportSource {
CoreStore.assert(
self.isRunningInAllowedQueue(),

View File

@@ -190,7 +190,7 @@ public extension ImportableUniqueObject where Self: DynamicObject {
.setValue(
newValue,
forKvcKey: type(of: self).uniqueIDKeyPath,
willSetValue: { $0.cs_toImportableNativeType() }
willSetValue: { ($0.cs_toImportableNativeType() as! CoreDataNativeType) }
)
}
}

View File

@@ -48,7 +48,7 @@ internal struct EntityIdentifier: Hashable {
internal init(_ type: NSManagedObject.Type) {
self.category = .coreData
self.interfacedClassName = String(reflecting: type)
self.interfacedClassName = NSStringFromClass(type)
}
internal init(_ type: CoreStoreObject.Type) {

View File

@@ -54,6 +54,9 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
@nonobjc
internal var enabled = true
@nonobjc
internal let taskGroup = DispatchGroup()
@nonobjc
internal weak var handler: FetchedResultsControllerHandler?
@@ -78,6 +81,7 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
@objc
dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.taskGroup.enter()
guard self.enabled else {
return
@@ -92,6 +96,10 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
@objc
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
defer {
self.taskGroup.leave()
}
guard self.enabled else {
return

View File

@@ -38,23 +38,29 @@ internal extension NSEntityDescription {
guard let userInfo = self.userInfo,
let typeName = userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] as! String?,
let entityName = userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] as! String? else {
let entityName = userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] as! String?,
let isAbstract = userInfo[UserInfoKey.CoreStoreManagedObjectIsAbstract] as! Bool? else {
return nil
}
return CoreStoreSchema.AnyEntity(
type: NSClassFromString(typeName) as! CoreStoreObject.Type,
entityName: entityName
entityName: entityName,
isAbstract: isAbstract,
versionHashModifier: userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] as! String?
)
}
set {
if let newValue = newValue {
self.userInfo = [
var userInfo: [AnyHashable : Any] = [
UserInfoKey.CoreStoreManagedObjectTypeName: NSStringFromClass(newValue.type),
UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName
UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName,
UserInfoKey.CoreStoreManagedObjectIsAbstract: newValue.isAbstract
]
userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = newValue.versionHashModifier
self.userInfo = userInfo
}
else {
@@ -72,5 +78,7 @@ internal extension NSEntityDescription {
fileprivate static let CoreStoreManagedObjectTypeName = "CoreStoreManagedObjectTypeName"
fileprivate static let CoreStoreManagedObjectEntityName = "CoreStoreManagedObjectEntityName"
fileprivate static let CoreStoreManagedObjectIsAbstract = "CoreStoreManagedObjectIsAbstract"
fileprivate static let CoreStoreManagedObjectVersionHashModifier = "CoreStoreManagedObjectVersionHashModifier"
}
}

View File

@@ -0,0 +1,61 @@
//
// NSManagedObject+DynamicModel.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
import CoreData
// MARK: - NSManagedObject
internal extension NSManagedObject {
@nonobjc
internal weak var coreStoreObject: CoreStoreObject? {
get {
return cs_getAssociatedObjectForKey(
&PropertyKeys.coreStoreObject,
inObject: self
)
}
set {
cs_setAssociatedWeakObject(
newValue,
forKey: &PropertyKeys.coreStoreObject,
inObject: self
)
}
}
// MARK: Private
private struct PropertyKeys {
static var coreStoreObject: Void?
}
}

View File

@@ -48,6 +48,25 @@ internal extension NSManagedObject {
}
return nil
}
@nonobjc
internal func isEditableInContext() -> Bool? {
guard let context = self.managedObjectContext else {
return nil
}
if context.isTransactionContext {
return true
}
if context.isDataStackContext {
return false
}
return nil
}
// TODO: test before release (rolled back)
// @nonobjc
// internal static func cs_swizzleMethodsForLogging() {

View File

@@ -400,9 +400,9 @@ extension LegacySQLiteStore: CustomDebugStringConvertible, CoreStoreDebugStringC
}
// MARK: - LegacyXcodeDataModel
// MARK: - LegacyXcodeDataModelSchema
extension LegacyXcodeDataModel: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
extension LegacyXcodeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -1045,9 +1045,9 @@ extension VersionLock: CustomStringConvertible, CustomDebugStringConvertible, Co
}
// MARK: - XcodeDataModel
// MARK: - XcodeDataModelSchema
extension XcodeDataModel: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
extension XcodeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible

View File

@@ -0,0 +1,133 @@
//
// MigrationMappingProvider.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: - SchemaMappingProvider
public protocol SchemaMappingProvider {
var sourceSchema: DynamicSchema { get }
var destinationSchema: DynamicSchema { get }
func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType)
}
public protocol EntityMappingProvider {
var source: (schema: DynamicSchema, entity: DynamicEntity) { get }
var destination: (schema: DynamicSchema, entity: DynamicEntity) { get }
func createEntityMapping() -> NSEntityMapping
}
// MARK: - XcodeMappingModelProvider
open class XcodeMappingModelProvider<S: DynamicSchema, D: DynamicSchema>: SchemaMappingProvider {
private let mappingModelBundles: [Bundle]
public required init(source: SourceSchema, destination: DestinationSchema, mappingModelBundles: [Bundle] = Bundle.allBundles) {
self.sourceSchema = source
self.destinationSchema = destination
self.mappingModelBundles = mappingModelBundles
}
// MARK: SchemaMappingProvider
public typealias SourceSchema = S
public typealias DestinationSchema = D
public let sourceSchema: SourceSchema
public let destinationSchema: DestinationSchema
public func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
let sourceModel = self.sourceSchema.rawModel()
let destinationModel = self.destinationSchema.rawModel()
if let mappingModel = NSMappingModel(
from: self.mappingModelBundles,
forSourceModel: sourceModel,
destinationModel: destinationModel) {
return (
mappingModel,
.heavyweight(
sourceVersion: self.sourceSchema.modelVersion,
destinationVersion: self.destinationSchema.modelVersion
)
)
}
let mappingModel = try NSMappingModel.inferredMappingModel(
forSourceModel: sourceModel,
destinationModel: destinationModel
)
return (
mappingModel,
.lightweight(
sourceVersion: self.sourceSchema.modelVersion,
destinationVersion: self.destinationSchema.modelVersion
)
)
}
}
// MARK: - UnsafeMigrationProxyObject
public final class UnsafeMigrationProxyObject {
public subscript(kvcKey: KeyPath) -> Any? {
get {
return self.rawObject.cs_accessValueForKVCKey(kvcKey)
}
set {
self.rawObject.cs_setValue(newValue, forKVCKey: kvcKey)
}
}
// MARK: Internal
internal init(_ rawObject: NSManagedObject) {
self.rawObject = rawObject
}
// MARK: Private
private let rawObject: NSManagedObject
}

View File

@@ -546,7 +546,7 @@ public final class CSListMonitor: NSObject {
// MARK: - ListMonitor
@available(OSX 10.12, *)
extension ListMonitor where T: NSManagedObject {
extension ListMonitor where ListMonitor.ObjectType: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -138,7 +138,7 @@ public final class CSObjectMonitor: NSObject {
// MARK: - ObjectMonitor
@available(OSX 10.12, *)
extension ObjectMonitor where EntityType: NSManagedObject {
extension ObjectMonitor where ObjectMonitor.ObjectType: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -38,7 +38,7 @@ public extension CoreStore {
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
public static func monitorObject<T: NSManagedObject>(_ object: T) -> ObjectMonitor<T> {
public static func monitorObject<T: DynamicObject>(_ object: T) -> ObjectMonitor<T> {
return self.defaultStack.monitorObject(object)
}
@@ -50,7 +50,7 @@ public extension CoreStore {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorList<T: NSManagedObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
public static func monitorList<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.defaultStack.monitorList(from, fetchClauses)
}
@@ -62,7 +62,7 @@ public extension CoreStore {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorList<T: NSManagedObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
public static func monitorList<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
return self.defaultStack.monitorList(from, fetchClauses)
}
@@ -74,7 +74,7 @@ public extension CoreStore {
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
public static func monitorList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
@@ -86,7 +86,7 @@ public extension CoreStore {
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
public static func monitorList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
@@ -99,7 +99,7 @@ public extension CoreStore {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorSectionedList<T: NSManagedObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
public static func monitorSectionedList<T: DynamicObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
}
@@ -112,7 +112,7 @@ public extension CoreStore {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorSectionedList<T: NSManagedObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
public static func monitorSectionedList<T: DynamicObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
}
@@ -125,7 +125,7 @@ public extension CoreStore {
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorSectionedList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
public static func monitorSectionedList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
@@ -138,7 +138,7 @@ public extension CoreStore {
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorSectionedList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
public static func monitorSectionedList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}

View File

@@ -38,7 +38,7 @@ public extension DataStack {
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
public func monitorObject<T: NSManagedObject>(_ object: T) -> ObjectMonitor<T> {
public func monitorObject<T: DynamicObject>(_ object: T) -> ObjectMonitor<T> {
CoreStore.assert(
Thread.isMainThread,
@@ -54,7 +54,7 @@ public extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorList<T: NSManagedObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
public func monitorList<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.monitorList(from, fetchClauses)
}
@@ -66,7 +66,7 @@ public extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorList<T: NSManagedObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
public func monitorList<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
CoreStore.assert(
Thread.isMainThread,
@@ -95,7 +95,7 @@ public extension DataStack {
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
public func monitorList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
@@ -107,7 +107,7 @@ public extension DataStack {
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
public func monitorList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
CoreStore.assert(
Thread.isMainThread,
@@ -138,7 +138,7 @@ public extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorSectionedList<T: NSManagedObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
public func monitorSectionedList<T: DynamicObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.monitorSectionedList(from, sectionBy, fetchClauses)
}
@@ -151,7 +151,7 @@ public extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorSectionedList<T: NSManagedObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
public func monitorSectionedList<T: DynamicObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
CoreStore.assert(
Thread.isMainThread,
@@ -182,7 +182,7 @@ public extension DataStack {
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
public func monitorSectionedList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
@@ -195,7 +195,7 @@ public extension DataStack {
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
public func monitorSectionedList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
CoreStore.assert(
Thread.isMainThread,

View File

@@ -67,17 +67,22 @@ import CoreData
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
*/
@available(OSX 10.12, *)
public final class ListMonitor<T: DynamicObject>: Hashable {
public final class ListMonitor<D: DynamicObject>: Hashable {
// MARK: Public (Accessors)
/**
The type for the objects contained bye the `ListMonitor`
*/
public typealias ObjectType = D
/**
Returns the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`.
- parameter index: the index of the object. Using an index above the valid range will raise an exception.
- returns: the `NSManagedObject` at the specified index
*/
public subscript(index: Int) -> T {
public subscript(index: Int) -> ObjectType {
CoreStore.assert(
!self.isPendingRefetch || Thread.isMainThread,
@@ -85,7 +90,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
if self.isSectioned {
return T.cs_fromRaw(object: self.fetchedResultsController.fetchedObjects![index])
return ObjectType.cs_fromRaw(object: self.fetchedResultsController.fetchedObjects![index])
}
return self[0, index]
}
@@ -96,14 +101,14 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter index: the index for the object. Using an index above the valid range will return `nil`.
- returns: the `NSManagedObject` at the specified index, or `nil` if out of bounds
*/
public subscript(safeIndex index: Int) -> T? {
public subscript(safeIndex index: Int) -> ObjectType? {
if self.isSectioned {
let fetchedObjects = self.fetchedResultsController.fetchedObjects!
if index < fetchedObjects.count && index >= 0 {
return T.cs_fromRaw(object: fetchedObjects[index])
return ObjectType.cs_fromRaw(object: fetchedObjects[index])
}
return nil
}
@@ -117,7 +122,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will raise an exception.
- returns: the `NSManagedObject` at the specified section and item index
*/
public subscript(sectionIndex: Int, itemIndex: Int) -> T {
public subscript(sectionIndex: Int, itemIndex: Int) -> ObjectType {
return self[NSIndexPath(indexes: [sectionIndex, itemIndex], length: 2) as IndexPath]
}
@@ -129,7 +134,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`.
- returns: the `NSManagedObject` at the specified section and item index, or `nil` if out of bounds
*/
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> T? {
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> ObjectType? {
guard let section = self.sectionInfoAtIndex(safeSectionIndex: sectionIndex) else {
@@ -139,7 +144,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
return nil
}
return T.cs_fromRaw(object: section.objects![itemIndex] as! NSManagedObject)
return ObjectType.cs_fromRaw(object: section.objects![itemIndex] as! NSManagedObject)
}
/**
@@ -148,13 +153,13 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will raise an exception.
- returns: the `NSManagedObject` at the specified index path
*/
public subscript(indexPath: IndexPath) -> T {
public subscript(indexPath: IndexPath) -> ObjectType {
CoreStore.assert(
!self.isPendingRefetch || Thread.isMainThread,
"Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress."
)
return T.cs_fromRaw(object: self.fetchedResultsController.object(at: indexPath))
return ObjectType.cs_fromRaw(object: self.fetchedResultsController.object(at: indexPath))
}
/**
@@ -163,7 +168,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will return `nil`.
- returns: the `NSManagedObject` at the specified index path, or `nil` if out of bounds
*/
public subscript(safeIndexPath indexPath: IndexPath) -> T? {
public subscript(safeIndexPath indexPath: IndexPath) -> ObjectType? {
return self[
safeSectionIndex: indexPath[0],
@@ -340,7 +345,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter object: the `NSManagedObject` to search the index of
- returns: the index of the `NSManagedObject` if it exists in the `ListMonitor`'s fetched objects, or `nil` if not found.
*/
public func indexOf(_ object: T) -> Int? {
public func indexOf(_ object: ObjectType) -> Int? {
CoreStore.assert(
!self.isPendingRefetch || Thread.isMainThread,
@@ -359,7 +364,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter object: the `NSManagedObject` to search the index of
- returns: the `NSIndexPath` of the `NSManagedObject` if it exists in the `ListMonitor`'s fetched objects, or `nil` if not found.
*/
public func indexPathOf(_ object: T) -> IndexPath? {
public func indexPathOf(_ object: ObjectType) -> IndexPath? {
CoreStore.assert(
!self.isPendingRefetch || Thread.isMainThread,
@@ -382,7 +387,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter observer: a `ListObserver` to send change notifications to
*/
public func addObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == T {
public func addObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == ObjectType {
self.unregisterObserver(observer)
self.registerObserver(
@@ -417,7 +422,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter observer: a `ListObjectObserver` to send change notifications to
*/
public func addObserver<U: ListObjectObserver>(_ observer: U) where U.ListEntityType == T {
public func addObserver<U: ListObjectObserver>(_ observer: U) where U.ListEntityType == ObjectType {
self.unregisterObserver(observer)
self.registerObserver(
@@ -471,7 +476,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter observer: a `ListSectionObserver` to send change notifications to
*/
public func addObserver<U: ListSectionObserver>(_ observer: U) where U.ListEntityType == T {
public func addObserver<U: ListSectionObserver>(_ observer: U) where U.ListEntityType == ObjectType {
self.unregisterObserver(observer)
self.registerObserver(
@@ -532,7 +537,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
- parameter observer: a `ListObserver` to unregister notifications to
*/
public func removeObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == T {
public func removeObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == ObjectType {
self.unregisterObserver(observer)
}
@@ -550,7 +555,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
`refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Note that only specified clauses will be changed; unspecified clauses will use previous values.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
*/
public func refetch(_ fetchClauses: FetchClause...) {
@@ -562,7 +567,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
`refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Note that only specified clauses will be changed; unspecified clauses will use previous values.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
*/
public func refetch(_ fetchClauses: [FetchClause]) {
@@ -573,9 +578,24 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
}
// MARK: Public (3rd Party Utilities)
/**
Allow external libraries to store custom data in the `ListMonitor`. App code should rarely have a need for this.
```
enum Static {
static var myDataKey: Void?
}
monitor.userInfo[&Static.myDataKey] = myObject
```
- Important: Do not use this method to store thread-sensitive data.
*/
private let userInfo = UserInfo()
// MARK: Equatable
public static func == (lhs: ListMonitor<T>, rhs: ListMonitor<T>) -> Bool {
public static func == (lhs: ListMonitor<ObjectType>, rhs: ListMonitor<ObjectType>) -> Bool {
return lhs.fetchedResultsController === rhs.fetchedResultsController
}
@@ -585,7 +605,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
return lhs.fetchedResultsController === rhs.fetchedResultsController
}
public static func ~= (lhs: ListMonitor<T>, rhs: ListMonitor<T>) -> Bool {
public static func ~= (lhs: ListMonitor<ObjectType>, rhs: ListMonitor<ObjectType>) -> Bool {
return lhs.fetchedResultsController === rhs.fetchedResultsController
}
@@ -606,7 +626,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
// MARK: Internal
internal convenience init(dataStack: DataStack, from: From<T>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) {
self.init(
context: dataStack.mainContext,
@@ -618,7 +638,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal convenience init(dataStack: DataStack, from: From<T>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<T>) -> Void) {
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<ObjectType>) -> Void) {
self.init(
context: dataStack.mainContext,
@@ -630,7 +650,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<T>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) {
self.init(
context: unsafeTransaction.context,
@@ -642,7 +662,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<T>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<T>) -> Void) {
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<ObjectType>) -> Void) {
self.init(
context: unsafeTransaction.context,
@@ -654,7 +674,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<T>) -> Void) {
internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<ObjectType>) -> Void) {
cs_setAssociatedRetainedObject(
NotificationObserver(
@@ -674,7 +694,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<T>, _ object: T, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) {
internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<ObjectType>, _ object: ObjectType, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) {
cs_setAssociatedRetainedObject(
NotificationObserver(
@@ -684,7 +704,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
guard let `self` = self,
let userInfo = note.userInfo,
let object = userInfo[String(describing: NSManagedObject.self)] as? T else {
let object = userInfo[String(describing: NSManagedObject.self)] as? ObjectType else {
return
}
@@ -701,7 +721,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<T>, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) {
internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<ObjectType>, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) {
cs_setAssociatedRetainedObject(
NotificationObserver(
@@ -724,7 +744,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor<T>) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor<T>) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<T>) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<T>) -> Void) {
internal func registerObserver<U: AnyObject>(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>) -> Void) {
CoreStore.assert(
Thread.isMainThread,
@@ -784,7 +804,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor<T>, _ object: T, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor<T>, _ object: T, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor<T>, _ object: T, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor<T>, _ object: T, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) {
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>, _ object: ObjectType, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>, _ object: ObjectType, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>, _ object: ObjectType, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>, _ object: ObjectType, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) {
CoreStore.assert(
Thread.isMainThread,
@@ -845,7 +865,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor<T>, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor<T>, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) {
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor<ObjectType>, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) {
CoreStore.assert(
Thread.isMainThread,
@@ -926,8 +946,12 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
return
}
self.fetchedResultsControllerDelegate.enabled = false
self.applyFetchClauses(self.fetchedResultsController.fetchRequest)
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = ListMonitor.recreateFetchedResultsController(
context: self.fetchedResultsController.managedObjectContext,
from: self.from,
sectionBy: self.sectionBy,
applyFetchClauses: self.applyFetchClauses
)
self.transactionQueue.async { [weak self] in
@@ -936,16 +960,21 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
return
}
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
DispatchQueue.main.async { [weak self] () -> Void in
try! newFetchedResultsController.performFetchFromSpecifiedStores()
self.fetchedResultsControllerDelegate.taskGroup.notify(queue: .main) {
self.fetchedResultsControllerDelegate.enabled = false
}
newFetchedResultsControllerDelegate.taskGroup.notify(queue: .main) { [weak self] () -> Void in
guard let `self` = self else {
return
}
self.fetchedResultsControllerDelegate.enabled = true
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = (newFetchedResultsController, newFetchedResultsControllerDelegate)
newFetchedResultsControllerDelegate.handler = self
self.isPendingRefetch = false
NotificationCenter.default.post(
@@ -966,7 +995,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
// MARK: Private
fileprivate let fetchedResultsController: CoreStoreFetchedResultsController
fileprivate var fetchedResultsController: CoreStoreFetchedResultsController
fileprivate let taskGroup = DispatchGroup()
fileprivate let sectionIndexTransformer: (_ sectionName: KeyPath?) -> String?
@@ -985,7 +1014,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
private var didInsertSectionKey: Void?
private var didDeleteSectionKey: Void?
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
private var fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
private var observerForWillChangePersistentStore: NotificationObserver!
private var observerForDidChangePersistentStore: NotificationObserver!
private let transactionQueue: DispatchQueue
@@ -1012,9 +1041,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
}
}
private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From<T>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((ListMonitor<T>) -> Void)?) {
self.isSectioned = (sectionBy != nil)
private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) -> (controller: CoreStoreFetchedResultsController, delegate: FetchedResultsControllerDelegate) {
let fetchRequest = CoreStoreFetchRequest()
fetchRequest.fetchLimit = 0
@@ -1032,9 +1059,25 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
)
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
self.fetchedResultsController = fetchedResultsController
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
return (fetchedResultsController, fetchedResultsControllerDelegate)
}
private let from: From<ObjectType>
private let sectionBy: SectionBy?
private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From<ObjectType>, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((ListMonitor<ObjectType>) -> Void)?) {
self.isSectioned = (sectionBy != nil)
self.from = from
self.sectionBy = sectionBy
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = ListMonitor.recreateFetchedResultsController(
context: context,
from: from,
sectionBy: sectionBy,
applyFetchClauses: applyFetchClauses
)
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
@@ -1046,9 +1089,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
}
self.transactionQueue = transactionQueue
self.applyFetchClauses = applyFetchClauses
fetchedResultsControllerDelegate.handler = self
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
self.fetchedResultsControllerDelegate.handler = self
guard let coordinator = context.parentStack?.coordinator else {
@@ -1109,7 +1150,7 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
transactionQueue.async {
try! fetchedResultsController.performFetchFromSpecifiedStores()
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
self.taskGroup.notify(queue: .main) {
createAsynchronously(self)
@@ -1118,29 +1159,29 @@ public final class ListMonitor<T: DynamicObject>: Hashable {
}
else {
try! fetchedResultsController.performFetchFromSpecifiedStores()
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
}
}
}
// MARK: - ListMonitor where T: NSManagedObject
// MARK: - ListMonitor where ListMonitor.ObjectType: NSManagedObject
@available(OSX 10.12, *)
extension ListMonitor where T: NSManagedObject {
extension ListMonitor where ListMonitor.ObjectType: NSManagedObject {
/**
Returns all objects in all sections
- returns: all objects in all sections
*/
public func objectsInAllSections() -> [T] {
public func objectsInAllSections() -> [ObjectType] {
CoreStore.assert(
!self.isPendingRefetch || Thread.isMainThread,
"Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress."
)
return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController<T>).fetchedObjects ?? []
return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController<ObjectType>).fetchedObjects ?? []
}
/**
@@ -1149,9 +1190,9 @@ extension ListMonitor where T: NSManagedObject {
- parameter section: the section index. Using an index outside the valid range will raise an exception.
- returns: all objects in the specified section
*/
public func objectsInSection(_ section: Int) -> [T] {
public func objectsInSection(_ section: Int) -> [ObjectType] {
return (self.sectionInfoAtIndex(section).objects as! [T]?) ?? []
return (self.sectionInfoAtIndex(section).objects as! [ObjectType]?) ?? []
}
/**
@@ -1160,31 +1201,31 @@ extension ListMonitor where T: NSManagedObject {
- parameter section: the section index. Using an index outside the valid range will return `nil`.
- returns: all objects in the specified section
*/
public func objectsInSection(safeSectionIndex section: Int) -> [T]? {
public func objectsInSection(safeSectionIndex section: Int) -> [ObjectType]? {
return self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as! [T]?
return self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as! [ObjectType]?
}
}
// MARK: - ListMonitor where T: CoreStoreObject
// MARK: - ListMonitor where ListMonitor.ObjectType: CoreStoreObject
@available(OSX 10.12, *)
extension ListMonitor where T: CoreStoreObject {
extension ListMonitor where ListMonitor.ObjectType: CoreStoreObject {
/**
Returns all objects in all sections
- returns: all objects in all sections
*/
public func objectsInAllSections() -> [T] {
public func objectsInAllSections() -> [ObjectType] {
CoreStore.assert(
!self.isPendingRefetch || Thread.isMainThread,
"Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress."
)
return (self.fetchedResultsController.fetchedObjects ?? [])
.map(T.cs_fromRaw)
.map(ObjectType.cs_fromRaw)
}
/**
@@ -1193,10 +1234,10 @@ extension ListMonitor where T: CoreStoreObject {
- parameter section: the section index. Using an index outside the valid range will raise an exception.
- returns: all objects in the specified section
*/
public func objectsInSection(_ section: Int) -> [T] {
public func objectsInSection(_ section: Int) -> [ObjectType] {
return (self.sectionInfoAtIndex(section).objects ?? [])
.map({ T.cs_fromRaw(object: $0 as! NSManagedObject) })
.map({ ObjectType.cs_fromRaw(object: $0 as! NSManagedObject) })
}
/**
@@ -1205,10 +1246,10 @@ extension ListMonitor where T: CoreStoreObject {
- parameter section: the section index. Using an index outside the valid range will return `nil`.
- returns: all objects in the specified section
*/
public func objectsInSection(safeSectionIndex section: Int) -> [T]? {
public func objectsInSection(safeSectionIndex section: Int) -> [ObjectType]? {
return (self.sectionInfoAtIndex(safeSectionIndex: section)?.objects)?
.map({ T.cs_fromRaw(object: $0 as! NSManagedObject) })
.map({ ObjectType.cs_fromRaw(object: $0 as! NSManagedObject) })
}
}
@@ -1306,12 +1347,15 @@ extension ListMonitor: FetchedResultsControllerHandler {
}
internal func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
defer {
self.taskGroup.leave()
}
NotificationCenter.default.post(
name: Notification.Name.listMonitorDidChangeList,
object: self
)
self.taskGroup.leave()
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {

View File

@@ -40,17 +40,22 @@ import CoreData
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
*/
@available(OSX 10.12, *)
public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
public final class ObjectMonitor<D: DynamicObject>: Equatable {
/**
The type for the object contained by the `ObjectMonitor`
*/
public typealias ObjectType = D
/**
Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted.
*/
public var object: EntityType? {
public var object: ObjectType? {
return self.fetchedResultsController
.fetchedObjects?
.first
.flatMap({ EntityType.cs_fromRaw(object: $0) })
.flatMap({ ObjectType.cs_fromRaw(object: $0) })
}
/**
@@ -72,7 +77,7 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
- parameter observer: an `ObjectObserver` to send change notifications to
*/
public func addObserver<U: ObjectObserver>(_ observer: U) where U.ObjectEntityType == EntityType {
public func addObserver<U: ObjectObserver>(_ observer: U) where U.ObjectEntityType == ObjectType {
self.unregisterObserver(observer)
self.registerObserver(
@@ -99,15 +104,30 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
- parameter observer: an `ObjectObserver` to unregister notifications to
*/
public func removeObserver<U: ObjectObserver>(_ observer: U) where U.ObjectEntityType == EntityType {
public func removeObserver<U: ObjectObserver>(_ observer: U) where U.ObjectEntityType == ObjectType {
self.unregisterObserver(observer)
}
// MARK: Public (3rd Party Utilities)
/**
Allow external libraries to store custom data in the `ObjectMonitor`. App code should rarely have a need for this.
```
enum Static {
static var myDataKey: Void?
}
monitor.userInfo[&Static.myDataKey] = myObject
```
- Important: Do not use this method to store thread-sensitive data.
*/
private let userInfo = UserInfo()
// MARK: Equatable
public static func == <T: DynamicObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
public static func == (lhs: ObjectMonitor<ObjectType>, rhs: ObjectMonitor<ObjectType>) -> Bool {
return lhs === rhs
}
@@ -117,7 +137,7 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
return lhs.fetchedResultsController === rhs.fetchedResultsController
}
public static func ~= <T: DynamicObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
public static func ~= (lhs: ObjectMonitor<ObjectType>, rhs: ObjectMonitor<ObjectType>) -> Bool {
return lhs === rhs
}
@@ -138,17 +158,17 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
// MARK: Internal
internal convenience init(dataStack: DataStack, object: EntityType) {
internal convenience init(dataStack: DataStack, object: ObjectType) {
self.init(context: dataStack.mainContext, object: object)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: EntityType) {
internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: ObjectType) {
self.init(context: unsafeTransaction.context, object: object)
}
internal func registerObserver<U: AnyObject>(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<EntityType>, _ object: EntityType) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<EntityType>, _ object: EntityType) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<EntityType>, _ object: EntityType, _ changedPersistentKeys: Set<String>) -> Void) {
internal func registerObserver<U: AnyObject>(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<ObjectType>, _ object: ObjectType) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<ObjectType>, _ object: ObjectType) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<ObjectType>, _ object: ObjectType, _ changedPersistentKeys: Set<String>) -> Void) {
CoreStore.assert(
Thread.isMainThread,
@@ -238,7 +258,7 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
private var didDeleteObjectKey: Void?
private var didUpdateObjectKey: Void?
private init(context: NSManagedObjectContext, object: EntityType) {
private init(context: NSManagedObjectContext, object: ObjectType) {
let rawObject = object.cs_toRaw()
let fetchRequest = CoreStoreFetchRequest()
@@ -253,7 +273,7 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
let fetchedResultsController = CoreStoreFetchedResultsController(
context: context,
fetchRequest: fetchRequest.dynamicCast(),
from: nil as From<EntityType>?,
from: nil as From<ObjectType>?,
applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest
)
@@ -269,7 +289,7 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
self.lastCommittedAttributes = (self.object?.cs_toRaw().committedValues(forKeys: nil) as? [String: NSObject]) ?? [:]
}
private func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor<EntityType>) -> Void) {
private func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor<ObjectType>) -> Void) {
cs_setAssociatedRetainedObject(
NotificationObserver(
@@ -289,7 +309,7 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
)
}
private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor<EntityType>, _ object: EntityType) -> Void) {
private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor<ObjectType>, _ object: ObjectType) -> Void) {
cs_setAssociatedRetainedObject(
NotificationObserver(
@@ -303,7 +323,7 @@ public final class ObjectMonitor<EntityType: DynamicObject>: Equatable {
return
}
callback(self, EntityType.cs_fromRaw(object: object))
callback(self, ObjectType.cs_fromRaw(object: object))
}
),
forKey: notificationKey,

View File

@@ -38,7 +38,7 @@ public extension UnsafeDataTransaction {
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
public func monitorObject<T: NSManagedObject>(_ object: T) -> ObjectMonitor<T> {
public func monitorObject<T: DynamicObject>(_ object: T) -> ObjectMonitor<T> {
return ObjectMonitor(
unsafeTransaction: self,
@@ -53,7 +53,7 @@ public extension UnsafeDataTransaction {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorList<T: NSManagedObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
public func monitorList<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.monitorList(from, fetchClauses)
}
@@ -65,7 +65,7 @@ public extension UnsafeDataTransaction {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorList<T: NSManagedObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
public func monitorList<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
@@ -90,7 +90,7 @@ public extension UnsafeDataTransaction {
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
public func monitorList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
@@ -102,7 +102,7 @@ public extension UnsafeDataTransaction {
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
public func monitorList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
@@ -129,7 +129,7 @@ public extension UnsafeDataTransaction {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorSectionedList<T: NSManagedObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
public func monitorSectionedList<T: DynamicObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.monitorSectionedList(from, sectionBy, fetchClauses)
}
@@ -142,7 +142,7 @@ public extension UnsafeDataTransaction {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorSectionedList<T: NSManagedObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
public func monitorSectionedList<T: DynamicObject>(_ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
@@ -168,7 +168,7 @@ public extension UnsafeDataTransaction {
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
public func monitorSectionedList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
@@ -181,7 +181,7 @@ public extension UnsafeDataTransaction {
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
public func monitorSectionedList<T: DynamicObject>(createAsynchronously: @escaping (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,

View File

@@ -96,6 +96,14 @@ public final class DataStack: Equatable {
return self.schemaHistory.currentModelVersion
}
/**
Returns the `DataStack`'s model schema.
*/
public var modelSchema: DynamicSchema {
return self.schemaHistory.schemaByVersion[self.schemaHistory.currentModelVersion]!
}
/**
Returns the entity name-to-class type mapping from the `DataStack`'s model.
*/
@@ -141,7 +149,7 @@ public final class DataStack: Equatable {
let actualType = anyEntity.type
if (actualType as AnyClass).isSubclass(of: type) {
entityTypesByName[entityDescription.name!] = actualType
entityTypesByName[entityDescription.name!] = (actualType as! CoreStoreObject.Type)
}
}
}
@@ -442,6 +450,21 @@ public final class DataStack: Equatable {
}
// MARK: 3rd Party Utilities
/**
Allow external libraries to store custom data in the `DataStack`. App code should rarely have a need for this.
```
enum Static {
static var myDataKey: Void?
}
CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject
```
- Important: Do not use this method to store thread-sensitive data.
*/
private let userInfo = UserInfo()
// MARK: Equatable
public static func == (lhs: DataStack, rhs: DataStack) -> Bool {
@@ -596,14 +619,14 @@ public final class DataStack: Equatable {
- parameter model: the `NSManagedObjectModel` for the stack
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
*/
@available(*, deprecated: 3.1, message: "Use the new DataStack.init(schemaHistory:) initializer passing a LegacyXcodeDataModel instance as argument")
@available(*, deprecated: 3.1, message: "Use the new DataStack.init(schemaHistory:) initializer passing a LegacyXcodeDataModelSchema instance as argument")
public convenience init(model: NSManagedObjectModel, migrationChain: MigrationChain = nil) {
let modelVersion = migrationChain.leafVersions.first!
self.init(
schemaHistory: SchemaHistory(
allSchema: [
LegacyXcodeDataModel(
LegacyXcodeDataModelSchema(
modelName: modelVersion,
model: model
)

View File

@@ -29,15 +29,42 @@ import Foundation
// MARK: - CoreStoreObject
open class CoreStoreObject: DynamicObject, Hashable {
/**
The `CoreStoreObject` is an abstract class for creating CoreStore-managed objects that are more type-safe and more convenient than `NSManagedObject` subclasses. The model entities for `CoreStoreObject` subclasses are inferred from the subclasses' Swift declaration themselves; no .xcdatamodeld files needed. To declare persisted attributes and relationships for the `CoreStoreObject` subclass, declare properties of type `Value.Required<T>`, `Value.Optional<T>` for values, or `Relationship.ToOne<T>`, `Relationship.ToManyOrdered<T>`, `Relationship.ToManyUnordered<T>` for relationships.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let master = Relationship.ToOne<Person>("master")
}
class Person: CoreStoreObject {
let name = Value.Required<String>("name")
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
```
`CoreStoreObject` entities for a model version should be added to `CoreStoreSchema` instance.
- SeeAlso: CoreStoreSchema
- SeeAlso: CoreStoreObject.Value
- SeeAlso: CoreStoreObject.Relationship
*/
open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
public required init(_ object: NSManagedObject) {
/**
Do not call this directly. This is exposed as public only as a required initializer.
- Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations.
*/
public required init(rawObject: NSManagedObject) {
self.isMeta = false
self.rawObject = object
self.initializeAttributes(Mirror(reflecting: self), { [unowned object] in object })
self.rawObject = rawObject
self.initializeAttributes(Mirror(reflecting: self), { [unowned self] in self })
}
/**
Do not call this directly. This is exposed as public only as a required initializer.
- Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations.
*/
public required init(asMeta: Void) {
self.isMeta = true
@@ -78,18 +105,18 @@ open class CoreStoreObject: DynamicObject, Hashable {
// MARK: Private
private func initializeAttributes(_ mirror: Mirror, _ accessRawObject: @escaping () -> NSManagedObject) {
private func initializeAttributes(_ mirror: Mirror, _ parentObject: @escaping () -> CoreStoreObject) {
_ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, accessRawObject) })
_ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, parentObject) })
for child in mirror.children {
switch child.value {
case let property as AttributeProtocol:
property.accessRawObject = accessRawObject
property.parentObject = parentObject
case let property as RelationshipProtocol:
property.accessRawObject = accessRawObject
property.parentObject = parentObject
default:
continue

View File

@@ -31,7 +31,7 @@ import Foundation
public final class CoreStoreSchema: DynamicSchema {
public convenience init(modelVersion: String, entities: [DynamicEntity], versionLock: VersionLock? = nil) {
public convenience init(modelVersion: ModelVersion, entities: [DynamicEntity], versionLock: VersionLock? = nil) {
self.init(
modelVersion: modelVersion,
@@ -40,7 +40,7 @@ public final class CoreStoreSchema: DynamicSchema {
)
}
public required init(modelVersion: String, entitiesByConfiguration: [String: [DynamicEntity]], versionLock: VersionLock? = nil) {
public required init(modelVersion: ModelVersion, entitiesByConfiguration: [String: [DynamicEntity]], versionLock: VersionLock? = nil) {
var actualEntitiesByConfiguration: [String: Set<AnyEntity>] = [:]
for (configuration, entities) in entitiesByConfiguration {
@@ -129,14 +129,20 @@ public final class CoreStoreSchema: DynamicSchema {
internal init(_ entity: DynamicEntity) {
self.type = entity.type
self.entityName = entity.entityName
self.init(
type: entity.type,
entityName: entity.entityName,
isAbstract: entity.isAbstract,
versionHashModifier: entity.versionHashModifier
)
}
internal init(type: CoreStoreObject.Type, entityName: String) {
internal init(type: DynamicObject.Type, entityName: String, isAbstract: Bool, versionHashModifier: String?) {
self.type = type
self.entityName = entityName
self.isAbstract = isAbstract
self.versionHashModifier = versionHashModifier
}
@@ -146,6 +152,8 @@ public final class CoreStoreSchema: DynamicSchema {
return lhs.type == rhs.type
&& lhs.entityName == rhs.entityName
&& lhs.isAbstract == rhs.isAbstract
&& lhs.versionHashModifier == rhs.versionHashModifier
}
// MARK: Hashable
@@ -154,12 +162,16 @@ public final class CoreStoreSchema: DynamicSchema {
return ObjectIdentifier(self.type).hashValue
^ self.entityName.hashValue
^ self.isAbstract.hashValue
^ (self.versionHashModifier ?? "").hashValue
}
// MARK: DynamicEntity
internal let type: CoreStoreObject.Type
internal let type: DynamicObject.Type
internal let entityName: EntityName
internal let isAbstract: Bool
internal let versionHashModifier: String?
}
@@ -193,6 +205,7 @@ public final class CoreStoreSchema: DynamicSchema {
let entityDescription = NSEntityDescription()
entityDescription.anyEntity = entity
entityDescription.name = entity.entityName
entityDescription.isAbstract = entity.isAbstract
entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self)
func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] {
@@ -210,17 +223,19 @@ public final class CoreStoreSchema: DynamicSchema {
description.isIndexed = attribute.isIndexed
description.defaultValue = attribute.defaultValue
description.isTransient = attribute.isTransient
// TODO: versionHash, renamingIdentifier, etc
description.versionHashModifier = attribute.versionHashModifier
description.renamingIdentifier = attribute.renamingIdentifier
propertyDescriptions.append(description)
case let relationship as RelationshipProtocol:
let description = NSRelationshipDescription()
description.name = relationship.keyPath
description.minCount = 0
description.maxCount = relationship.isToMany ? 0 : 1
description.minCount = relationship.minCount
description.maxCount = relationship.maxCount
description.isOrdered = relationship.isOrdered
description.deleteRule = relationship.deleteRule
// TODO: versionHash, renamingIdentifier, etc
description.versionHashModifier = relationship.versionHashModifier
description.renamingIdentifier = relationship.renamingIdentifier
propertyDescriptions.append(description)
default:
@@ -230,7 +245,7 @@ public final class CoreStoreSchema: DynamicSchema {
return propertyDescriptions
}
entityDescription.properties = createProperties(for: entity.type)
entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type)
return entityDescription
}
@@ -280,7 +295,7 @@ public final class CoreStoreSchema: DynamicSchema {
for (entity, entityDescription) in entityDescriptionsByEntity {
let relationshipsByName = relationshipsByNameByEntity[entity]!
for child in Mirror(reflecting: entity.type.meta).children {
for child in Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta).children {
switch child.value {
@@ -347,7 +362,7 @@ public final class CoreStoreSchema: DynamicSchema {
for (entity, entityDescription) in entityDescriptionsByEntity {
connectBaseEntity(
mirror: Mirror(reflecting: entity.type.meta),
mirror: Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta),
entityDescription: entityDescription
)
}

View File

@@ -29,9 +29,21 @@ import Foundation
// MARK: - DynamicSchema
/**
`DynamicSchema` are types that provide `NSManagedObjectModel` instances for a particular model version. CoreStore currently supports concrete types:
- `XcodeDataModelSchema`: describes models loaded from a .xcdatamodeld file.
- `LegacyXcodeDataModelSchema`: describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore.
- `CoreStoreSchema`: describes models written in `CoreStoreObject` Swift class declarations.
*/
public protocol DynamicSchema {
/**
The version string for this model schema.
*/
var modelVersion: ModelVersion { get }
/**
Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model.
*/
func rawModel() -> NSManagedObjectModel
}

View File

@@ -1,5 +1,5 @@
//
// LegacyXcodeDataModel.swift
// LegacyXcodeDataModelSchema.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
@@ -27,9 +27,9 @@ import CoreData
import Foundation
// MARK: - LegacyXcodeDataModel
// MARK: - LegacyXcodeDataModelSchema
public final class LegacyXcodeDataModel: DynamicSchema {
public final class LegacyXcodeDataModelSchema: DynamicSchema {
public required init(modelName: ModelVersion, model: NSManagedObjectModel) {

View File

@@ -1,5 +1,5 @@
//
// XcodeDataModel.swift
// XcodeDataModelSchema.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
@@ -27,9 +27,9 @@ import CoreData
import Foundation
// MARK: - XcodeDataModel
// MARK: - XcodeDataModelSchema
public final class XcodeDataModel: DynamicSchema {
public final class XcodeDataModelSchema: DynamicSchema {
public required init(modelVersion: ModelVersion, modelVersionFileURL: URL) {

View File

@@ -37,35 +37,6 @@ public protocol DynamicObject: class {
func cs_toRaw() -> NSManagedObject
}
public extension DynamicObject where Self: CoreStoreObject {
@inline(__always)
public static func keyPath<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Required<V>) -> String {
return attribute(self.meta).keyPath
}
@inline(__always)
public static func keyPath<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<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
@@ -114,12 +85,23 @@ extension CoreStoreObject {
context.assign(object, to: store)
}
return self.init(object)
return self.cs_fromRaw(object: object)
}
public class func cs_fromRaw(object: NSManagedObject) -> Self {
return self.init(object)
if let coreStoreObject = object.coreStoreObject {
@inline(__always)
func forceCast<T: CoreStoreObject>(_ value: CoreStoreObject) -> T {
return value as! T
}
return forceCast(coreStoreObject)
}
let coreStoreObject = self.init(rawObject: object)
object.coreStoreObject = coreStoreObject
return coreStoreObject
}
public func cs_toRaw() -> NSManagedObject {
@@ -127,3 +109,14 @@ extension CoreStoreObject {
return self.rawObject!
}
}
// MARK: - Internal
internal extension DynamicObject where Self: CoreStoreObject {
internal static var meta: Self {
return self.init(asMeta: ())
}
}

View File

@@ -32,31 +32,37 @@ import ObjectiveC
public protocol DynamicEntity {
var type: CoreStoreObject.Type { get }
var type: DynamicObject.Type { get }
var entityName: EntityName { get }
var isAbstract: Bool { get }
var versionHashModifier: String? { get }
}
// MARK: Entity
public struct Entity<O: CoreStoreObject>: DynamicEntity, Hashable {
public struct Entity<O: DynamicObject>: DynamicEntity, Hashable {
public init(_ entityName: String) {
public init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) {
self.init(O.self, entityName)
self.init(O.self, entityName, isAbstract: isAbstract, versionHashModifier: versionHashModifier)
}
public init(_ type: O.Type, _ entityName: String) {
public init(_ type: O.Type, _ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) {
self.type = type
self.entityName = entityName
self.isAbstract = isAbstract
self.versionHashModifier = versionHashModifier
}
// MARK: DynamicEntity
public let type: CoreStoreObject.Type
public let type: DynamicObject.Type
public let entityName: EntityName
public let isAbstract: Bool
public let versionHashModifier: String?
// MARK: Equatable
@@ -65,6 +71,8 @@ public struct Entity<O: CoreStoreObject>: DynamicEntity, Hashable {
return lhs.type == rhs.type
&& lhs.entityName == rhs.entityName
&& lhs.isAbstract == rhs.isAbstract
&& lhs.versionHashModifier == rhs.versionHashModifier
}
// MARK: Hashable
@@ -72,5 +80,8 @@ public struct Entity<O: CoreStoreObject>: DynamicEntity, Hashable {
public var hashValue: Int {
return ObjectIdentifier(self.type).hashValue
^ self.entityName.hashValue
^ self.isAbstract.hashValue
^ (self.versionHashModifier ?? "").hashValue
}
}

View File

@@ -60,52 +60,56 @@ public enum RelationshipContainer<O: CoreStoreObject> {
relationship.value = relationship2.value
}
public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public var value: D? {
get {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.accessRawObject()
.getValue(
forKvcKey: self.keyPath,
didGetValue: { $0.flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) }
)
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { $0.flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) }
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
self.accessRawObject()
.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { $0?.rawObject }
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { $0?.rawObject }
)
}
}
@@ -117,9 +121,13 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let isToMany = false
internal let isOrdered = false
internal let deleteRule: NSDeleteRule
internal let minCount: Int = 0
internal let maxCount: Int = 1
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var accessRawObject: () -> NSManagedObject = {
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
@@ -127,11 +135,13 @@ public enum RelationshipContainer<O: CoreStoreObject> {
// MARK: Private
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule) {
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
self.inverse = (D.self, inverseKeyPath)
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
}
}
@@ -157,24 +167,24 @@ public enum RelationshipContainer<O: CoreStoreObject> {
relationship.value = relationship2.value
}
public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
// TODO: add subscripts, indexed operations for more performant single updates
@@ -183,35 +193,39 @@ public enum RelationshipContainer<O: CoreStoreObject> {
get {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.accessRawObject()
.getValue(
forKvcKey: self.keyPath,
didGetValue: {
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: {
guard let orderedSet = $0 as! NSOrderedSet? else {
guard let orderedSet = $0 as! NSOrderedSet? else {
return []
}
return orderedSet.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })
return []
}
)
return orderedSet.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })
}
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
self.accessRawObject()
.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { NSOrderedSet(array: $0.map({ $0.rawObject! })) }
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { NSOrderedSet(array: $0.map({ $0.rawObject! })) }
)
}
}
@@ -224,9 +238,13 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let isOptional = true
internal let isOrdered = true
internal let deleteRule: NSDeleteRule
internal let minCount: Int
internal let maxCount: Int
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var accessRawObject: () -> NSManagedObject = {
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
@@ -234,11 +252,17 @@ public enum RelationshipContainer<O: CoreStoreObject> {
// MARK: Private
private init(keyPath: String, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule) {
private init(keyPath: String, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
self.inverse = (D.self, inverseKeyPath)
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
let range = (max(0, minCount) ... maxCount)
self.minCount = range.lowerBound
self.maxCount = range.upperBound
}
}
@@ -269,24 +293,24 @@ public enum RelationshipContainer<O: CoreStoreObject> {
relationship.value = Set(relationship2.value)
}
public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify) {
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule)
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
}
// TODO: add subscripts, indexed operations for more performant single updates
@@ -295,35 +319,39 @@ public enum RelationshipContainer<O: CoreStoreObject> {
get {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.accessRawObject()
.getValue(
forKvcKey: self.keyPath,
didGetValue: {
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: {
guard let set = $0 as! NSSet? else {
guard let set = $0 as! NSSet? else {
return []
}
return Set(set.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }))
return []
}
)
return Set(set.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }))
}
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
self.accessRawObject()
.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { NSSet(array: $0.map({ $0.rawObject! })) }
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { NSSet(array: $0.map({ $0.rawObject! })) }
)
}
}
@@ -336,9 +364,13 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let isOptional = true
internal let isOrdered = true
internal let deleteRule: NSDeleteRule
internal let minCount: Int
internal let maxCount: Int
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var accessRawObject: () -> NSManagedObject = {
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
@@ -346,11 +378,17 @@ public enum RelationshipContainer<O: CoreStoreObject> {
// MARK: Private
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule) {
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
self.inverse = (D.self, inverseKeyPath)
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
let range = (max(0, minCount) ... maxCount)
self.minCount = range.lowerBound
self.maxCount = range.upperBound
}
}
@@ -385,5 +423,9 @@ internal protocol RelationshipProtocol: class {
var isOrdered: Bool { get }
var deleteRule: NSDeleteRule { get }
var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get }
var accessRawObject: () -> NSManagedObject { get set }
var parentObject: () -> CoreStoreObject { get set }
var versionHashModifier: String? { get }
var renamingIdentifier: String? { get }
var minCount: Int { get }
var maxCount: Int { get }
}

View File

@@ -29,14 +29,30 @@ import Foundation
// MARK: - SchemaHistory
/**
The `SchemaHistory` encapsulates all model versions that is relevant to the data model, including past versions.
- SeeAlso: SchemaHistory.currentModelVersion
- SeeAlso: SchemaHistory.migrationChain
*/
public final class SchemaHistory: ExpressibleByArrayLiteral {
// MARK: -
/**
The version string for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`.
*/
public let currentModelVersion: ModelVersion
/**
The version string for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`.
*/
public let migrationChain: MigrationChain
/**
Initializes a `SchemaHistory` with all models declared in the specified (.xcdatamodeld) model file.
- Important: Use this initializer only if all model versions are either `XcodeDataModelSchema`s or `LegacyXcodeDataModelSchema`s. Do not use this initializer if even one of the model versions is a `CoreStoreSchema`; use the `SchemaHistory.init(allSchema:migrationChain:exactCurrentModelVersion:)` initializer instead.
- parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name (CFBundleName) will be used if it exists, or "CoreData" if it the bundle name was not set.
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
*/
public convenience init(modelName: XcodeDataModelFileName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) {
guard let modelFilePath = bundle.path(forResource: modelName, ofType: "momd") else {
@@ -92,7 +108,7 @@ public final class SchemaHistory: ExpressibleByArrayLiteral {
for modelVersion in modelVersions {
let fileURL = modelFileURL.appendingPathComponent("\(modelVersion).mom", isDirectory: false)
allSchema.append(XcodeDataModel(modelVersion: modelVersion, modelVersionFileURL: fileURL))
allSchema.append(XcodeDataModelSchema(modelVersion: modelVersion, modelVersionFileURL: fileURL))
}
self.init(
allSchema: allSchema,

View File

@@ -37,6 +37,7 @@ infix operator .= : AssignmentPrecedence
public extension DynamicObject where Self: CoreStoreObject {
public typealias Value = ValueContainer<Self>
public typealias Transformable = TransformableContainer<Self>
}
@@ -58,40 +59,61 @@ public enum ValueContainer<O: CoreStoreObject> {
attribute.value = attribute2.value
}
public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false) {
public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) {
self.keyPath = keyPath
self.isIndexed = isIndexed
self.isTransient = isTransient
self.defaultValue = `default`.cs_toImportableNativeType()
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
}
public var value: V {
get {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.accessRawObject()
.getValue(
forKvcKey: self.keyPath,
didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! }
)
return self.customGetter(
object,
{ () -> V in
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! }
)
}
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
self.accessRawObject()
.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { $0.cs_toImportableNativeType() }
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
self.customSetter(
object,
{ (newValue: V) -> Void in
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { $0.cs_toImportableNativeType() }
)
},
newValue
)
}
}
@@ -109,11 +131,19 @@ public enum ValueContainer<O: CoreStoreObject> {
internal let isIndexed: Bool
internal let isTransient: Bool
internal let defaultValue: Any?
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var accessRawObject: () -> NSManagedObject = {
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
// MARK: Private
private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V
private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void
}
@@ -136,39 +166,60 @@ public enum ValueContainer<O: CoreStoreObject> {
attribute.value = attribute2.value
}
public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false) {
public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) {
self.keyPath = keyPath
self.isTransient = isTransient
self.defaultValue = `default`?.cs_toImportableNativeType()
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
}
public var value: V? {
get {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.accessRawObject()
.getValue(
forKvcKey: self.keyPath,
didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) }
)
return self.customGetter(
object,
{ () -> V? in
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) }
)
}
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
self.accessRawObject().isRunningInAllowedQueue() == true,
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
self.accessRawObject()
.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { $0?.cs_toImportableNativeType() }
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
self.customSetter(
object,
{ (newValue: V?) -> Void in
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { $0?.cs_toImportableNativeType() }
)
},
newValue
)
}
}
@@ -185,11 +236,231 @@ public enum ValueContainer<O: CoreStoreObject> {
internal let isIndexed = false
internal let isTransient: Bool
internal let defaultValue: Any?
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var accessRawObject: () -> NSManagedObject = {
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
// MARK: Private
private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V?
private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void
}
}
// MARK: - TransformableContainer
public enum TransformableContainer<O: CoreStoreObject> {
// MARK: - Required
public final class Required<V: NSCoding & NSCopying>: AttributeProtocol {
public static func .= (_ attribute: TransformableContainer<O>.Required<V>, _ value: V) {
attribute.value = value
}
public static func .=<O2: CoreStoreObject> (_ attribute: TransformableContainer<O>.Required<V>, _ attribute2: TransformableContainer<O2>.Required<V>) {
attribute.value = attribute2.value
}
public init(_ keyPath: KeyPath, `default`: V, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) {
self.keyPath = keyPath
self.defaultValue = `default`
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
}
public var value: V {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.customGetter(
object,
{ () -> V in
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { $0 as! V }
)
}
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
self.customSetter(
object,
{ (newValue: V) -> Void in
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
)
},
newValue
)
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = false
internal let isIndexed: Bool
internal let isTransient: Bool
internal let defaultValue: Any?
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
// MARK: Private
private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V
private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void
}
// MARK: - Optional
public final class Optional<V: NSCoding & NSCopying>: AttributeProtocol {
public static func .= (_ attribute: TransformableContainer<O>.Optional<V>, _ value: V) {
attribute.value = value
}
public static func .=<O2: CoreStoreObject> (_ attribute: TransformableContainer<O>.Optional<V>, _ attribute2: TransformableContainer<O2>.Optional<V>) {
attribute.value = attribute2.value
}
public static func .=<O2: CoreStoreObject> (_ attribute: TransformableContainer<O>.Optional<V>, _ attribute2: TransformableContainer<O2>.Required<V>) {
attribute.value = attribute2.value
}
public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) {
self.keyPath = keyPath
self.defaultValue = `default`
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
}
public var value: V? {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.customGetter(
object,
{ () -> V? in
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { $0 as! V? }
)
}
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
self.customSetter(
object,
{ (newValue: V?) -> Void in
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
)
},
newValue
)
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = false
internal let isIndexed: Bool
internal let isTransient: Bool
internal let defaultValue: Any?
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
// MARK: Private
private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V?
private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void
}
}
@@ -205,5 +476,7 @@ internal protocol AttributeProtocol: class {
var isIndexed: Bool { get }
var isTransient: Bool { get }
var defaultValue: Any? { get }
var accessRawObject: () -> NSManagedObject { get set }
var versionHashModifier: String? { get }
var renamingIdentifier: String? { get }
var parentObject: () -> CoreStoreObject { get set }
}

View File

@@ -104,7 +104,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- parameter object: the `NSManagedObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
public override func edit<T: NSManagedObject>(_ object: T?) -> T? {
public override func edit<T: DynamicObject>(_ object: T?) -> T? {
CoreStore.assert(
!self.isCommitted,
@@ -121,7 +121,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
public override func edit<T: NSManagedObject>(_ into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
public override func edit<T: DynamicObject>(_ into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
CoreStore.assert(
!self.isCommitted,

View File

@@ -438,6 +438,21 @@ public /*abstract*/ class BaseDataTransaction {
}
// MARK: 3rd Party Utilities
/**
Allow external libraries to store custom data in the transaction. App code should rarely have a need for this.
```
enum Static {
static var myDataKey: Void?
}
transaction.userInfo[&Static.myDataKey] = myObject
```
- Important: Do not use this method to store thread-sensitive data.
*/
private let userInfo = UserInfo()
// MARK: Internal
internal let context: NSManagedObjectContext

View File

@@ -71,7 +71,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
- parameter object: the `NSManagedObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
public override func edit<T: NSManagedObject>(_ object: T?) -> T? {
public override func edit<T: DynamicObject>(_ object: T?) -> T? {
CoreStore.assert(
!self.isCommitted,
@@ -88,7 +88,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
public override func edit<T: NSManagedObject>(_ into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
public override func edit<T: DynamicObject>(_ into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
CoreStore.assert(
!self.isCommitted,