mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-18 06:57:15 +01:00
Merge branch 'corestore4_develop' of github.com:JohnEstropia/CoreStore into corestore4_develop
This commit is contained in:
287
Sources/Convenience/DynamicSchema+Convenience.swift
Normal file
287
Sources/Convenience/DynamicSchema+Convenience.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
115
Sources/Convenience/UserInfo.swift
Normal file
115
Sources/Convenience/UserInfo.swift
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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(),
|
||||
|
||||
@@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
61
Sources/Internal/NSManagedObject+DynamicModel.swift
Normal file
61
Sources/Internal/NSManagedObject+DynamicModel.swift
Normal 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?
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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? {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: ())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user