mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-28 12:11:56 +01:00
prototype new Fields as propertyWrappers (Swift 5.2 above only)
This commit is contained in:
@@ -29,7 +29,7 @@ import CoreData
|
||||
|
||||
// MARK: - AttributeProtocol
|
||||
|
||||
internal protocol AttributeProtocol: PropertyProtocol {
|
||||
internal protocol AttributeProtocol: AnyObject, PropertyProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
attributeType: NSAttributeType,
|
||||
|
||||
@@ -94,9 +94,10 @@ extension CoreStoreError: CustomDebugStringConvertible, CoreStoreDebugStringConv
|
||||
firstLine = ".progressiveMigrationRequired"
|
||||
info.append(("localStoreURL", localStoreURL))
|
||||
|
||||
case .asynchronousMigrationRequired(let localStoreURL):
|
||||
case .asynchronousMigrationRequired(let localStoreURL, let NSError):
|
||||
firstLine = ".asynchronousMigrationRequired"
|
||||
info.append(("localStoreURL", localStoreURL))
|
||||
info.append(("NSError", NSError))
|
||||
|
||||
case .internalError(let NSError):
|
||||
firstLine = ".internalError"
|
||||
|
||||
@@ -198,9 +198,6 @@ public enum CoreStoreError: Error, CustomNSError, Hashable {
|
||||
|
||||
case (.userError(let error1), .userError(let error2)):
|
||||
switch (error1, error2) {
|
||||
|
||||
case (let error1 as AnyHashable, let error2 as AnyHashable):
|
||||
return error1 == error2
|
||||
|
||||
case (let error1 as NSError, let error2 as NSError):
|
||||
return error1.isEqual(error2)
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
//
|
||||
// CoreStoreObject+DataSources.swift
|
||||
// CoreStore iOS
|
||||
// CoreStore
|
||||
//
|
||||
// Created by John Estropia on 2019/10/04.
|
||||
// Copyright © 2019 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2020 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.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
@@ -27,6 +27,33 @@ import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension DynamicObject where Self: CoreStoreObject {
|
||||
|
||||
public func observe<O, V>(_ keyPath: KeyPath<Self, FieldContainer<O>.Stored<V>>, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (Self, CoreStoreObjectValueDiff<V>) -> Void) -> CoreStoreObjectKeyValueObservation {
|
||||
|
||||
let result = _CoreStoreObjectKeyValueObservation(
|
||||
object: self.rawObject!,
|
||||
keyPath: self[keyPath: keyPath].keyPath,
|
||||
callback: { (object, kind, newValue, oldValue, _, isPrior) in
|
||||
|
||||
let notification = CoreStoreObjectValueDiff<V>(
|
||||
kind: kind,
|
||||
newNativeValue: newValue as? V.QueryableNativeType,
|
||||
oldNativeValue: oldValue as? V.QueryableNativeType,
|
||||
isPrior: isPrior
|
||||
)
|
||||
changeHandler(
|
||||
Self.cs_fromRaw(object: object),
|
||||
notification
|
||||
)
|
||||
}
|
||||
)
|
||||
result.start(options)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreObjectKeyValueObservation
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,88 @@
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
// MARK: - FieldContainer.Value
|
||||
|
||||
extension FieldContainer.Stored {
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where({ $0.nickname == "John" }))
|
||||
```
|
||||
*/
|
||||
public static func == (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
return Where(attribute.keyPath, isEqualTo: value)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is not equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where({ $0.nickname != "John" }))
|
||||
```
|
||||
*/
|
||||
public static func != (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
return !Where(attribute.keyPath, isEqualTo: value)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where({ $0.age < 20 }))
|
||||
```
|
||||
*/
|
||||
public static func < (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
return Where("%K < %@", attribute.keyPath, value.cs_toFieldStoredNativeType() as Any)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where({ $0.age > 20 }))
|
||||
```
|
||||
*/
|
||||
public static func > (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
return Where("%K > %@", attribute.keyPath, value.cs_toFieldStoredNativeType() as Any)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than or equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where({ $0.age <= 20 }))
|
||||
```
|
||||
*/
|
||||
public static func <= (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
return Where("%K <= %@", attribute.keyPath, value.cs_toFieldStoredNativeType() as Any)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than or equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where({ $0.age >= 20 }))
|
||||
```
|
||||
*/
|
||||
public static func >= (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
return Where("%K >= %@", attribute.keyPath, value.cs_toFieldStoredNativeType() as Any)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by checking if a sequence contains the value of a property
|
||||
```
|
||||
let dog = dataStack.fetchOne(From<Dog>().where({ ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname }))
|
||||
```
|
||||
*/
|
||||
public static func ~= <S: Sequence>(_ sequence: S, _ attribute: Self) -> Where<O> where S.Iterator.Element == V {
|
||||
|
||||
return Where(attribute.keyPath, isMemberOf: sequence)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ValueContainer.Required
|
||||
|
||||
extension ValueContainer.Required {
|
||||
|
||||
@@ -69,6 +69,11 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
|
||||
|
||||
self.isMeta = false
|
||||
self.rawObject = (rawObject as! CoreStoreManagedObject)
|
||||
|
||||
guard Self.meta.needsReflection else {
|
||||
|
||||
return
|
||||
}
|
||||
self.registerReceiver(
|
||||
mirror: Mirror(reflecting: self),
|
||||
object: self
|
||||
@@ -117,6 +122,11 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
|
||||
internal let rawObject: CoreStoreManagedObject?
|
||||
internal let isMeta: Bool
|
||||
|
||||
internal lazy var needsReflection: Bool = self.containsLegacyAttributes(
|
||||
mirror: Mirror(reflecting: self),
|
||||
object: self
|
||||
)
|
||||
|
||||
internal class func metaProperties(includeSuperclasses: Bool) -> [PropertyProtocol] {
|
||||
|
||||
func keyPaths(_ allKeyPaths: inout [PropertyProtocol], for dynamicType: CoreStoreObject.Type) {
|
||||
@@ -137,9 +147,33 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
|
||||
keyPaths(&allKeyPaths, for: self)
|
||||
return allKeyPaths
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func containsLegacyAttributes(mirror: Mirror, object: CoreStoreObject) -> Bool {
|
||||
|
||||
if let superclassMirror = mirror.superclassMirror,
|
||||
self.containsLegacyAttributes(mirror: superclassMirror, object: object) {
|
||||
|
||||
return true
|
||||
}
|
||||
for child in mirror.children {
|
||||
|
||||
switch child.value {
|
||||
|
||||
case is AttributeProtocol:
|
||||
return true
|
||||
|
||||
case is RelationshipProtocol:
|
||||
return true
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func registerReceiver(mirror: Mirror, object: CoreStoreObject) {
|
||||
|
||||
|
||||
@@ -286,6 +286,25 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
for property in type.metaProperties(includeSuperclasses: false) {
|
||||
|
||||
switch property {
|
||||
|
||||
case let attribute as FieldAttributeProtocol:
|
||||
Internals.assert(
|
||||
!NSManagedObject.instancesRespond(to: Selector(attribute.keyPath)),
|
||||
"Attribute Property name \"\(String(reflecting: entity.type)).\(attribute.keyPath)\" is not allowed because it collides with \"\(String(reflecting: NSManagedObject.self)).\(attribute.keyPath)\""
|
||||
)
|
||||
let entityDescriptionValues = attribute.entityDescriptionValues()
|
||||
let description = NSAttributeDescription()
|
||||
description.name = attribute.keyPath
|
||||
description.attributeType = entityDescriptionValues.attributeType
|
||||
description.isOptional = entityDescriptionValues.isOptional
|
||||
description.defaultValue = entityDescriptionValues.defaultValue
|
||||
description.isTransient = entityDescriptionValues.isTransient
|
||||
description.allowsExternalBinaryDataStorage = entityDescriptionValues.allowsExternalBinaryDataStorage
|
||||
description.versionHashModifier = entityDescriptionValues.versionHashModifier
|
||||
description.renamingIdentifier = entityDescriptionValues.renamingIdentifier
|
||||
propertyDescriptions.append(description)
|
||||
keyPathsByAffectedKeyPaths[attribute.keyPath] = entityDescriptionValues.affectedByKeyPaths
|
||||
customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter)
|
||||
|
||||
case let attribute as AttributeProtocol:
|
||||
Internals.assert(
|
||||
|
||||
@@ -153,41 +153,68 @@ extension CoreStoreObject {
|
||||
|
||||
public class func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any]? {
|
||||
|
||||
func initializeAttributes(mirror: Mirror, object: Self, into attributes: inout [KeyPathString: Any]) {
|
||||
var values: [KeyPathString: Any] = [:]
|
||||
if self.meta.needsReflection {
|
||||
|
||||
if let superClassMirror = mirror.superclassMirror {
|
||||
func initializeAttributes(mirror: Mirror, object: Self, into attributes: inout [KeyPathString: Any]) {
|
||||
|
||||
initializeAttributes(
|
||||
mirror: superClassMirror,
|
||||
object: object,
|
||||
into: &attributes
|
||||
)
|
||||
if let superClassMirror = mirror.superclassMirror {
|
||||
|
||||
initializeAttributes(
|
||||
mirror: superClassMirror,
|
||||
object: object,
|
||||
into: &attributes
|
||||
)
|
||||
}
|
||||
for child in mirror.children {
|
||||
|
||||
switch child.value {
|
||||
|
||||
case let property as FieldAttributeProtocol:
|
||||
attributes[property.keyPath] = type(of: property).read(field: property, for: object.rawObject!)
|
||||
|
||||
case let property as AttributeProtocol:
|
||||
attributes[property.keyPath] = property.valueForSnapshot
|
||||
|
||||
case let property as RelationshipProtocol:
|
||||
attributes[property.keyPath] = property.valueForSnapshot
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
for child in mirror.children {
|
||||
guard let object = context.fetchExisting(id) as CoreStoreObject? else {
|
||||
|
||||
switch child.value {
|
||||
return nil
|
||||
}
|
||||
initializeAttributes(
|
||||
mirror: Mirror(reflecting: object),
|
||||
object: object as! Self,
|
||||
into: &values
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
case let property as AttributeProtocol:
|
||||
attributes[property.keyPath] = property.valueForSnapshot
|
||||
guard
|
||||
let object = context.fetchExisting(id) as CoreStoreObject?,
|
||||
let rawObject = object.rawObject
|
||||
else {
|
||||
|
||||
case let property as RelationshipProtocol:
|
||||
attributes[property.keyPath] = property.valueForSnapshot
|
||||
return nil
|
||||
}
|
||||
for property in self.metaProperties(includeSuperclasses: true) {
|
||||
|
||||
switch property {
|
||||
|
||||
case let property as FieldAttributeProtocol:
|
||||
values[property.keyPath] = type(of: property).read(field: property, for: rawObject)
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
guard let object = context.fetchExisting(id) as CoreStoreObject? else {
|
||||
|
||||
return nil
|
||||
}
|
||||
var values: [KeyPathString: Any] = [:]
|
||||
initializeAttributes(
|
||||
mirror: Mirror(reflecting: object),
|
||||
object: object as! Self,
|
||||
into: &values
|
||||
)
|
||||
return values
|
||||
}
|
||||
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
//
|
||||
// DynamicObjectMeta.swift
|
||||
// CoreStore iOS
|
||||
//
|
||||
// Created by John Estropia on 2019/08/20.
|
||||
// Copyright © 2019 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
#if swift(>=5.1)
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - DynamicObjectMeta
|
||||
|
||||
@dynamicMemberLookup
|
||||
public struct DynamicObjectMeta<R, D>: CustomDebugStringConvertible {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public typealias Root = R
|
||||
public typealias Destination = D
|
||||
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return self.keyPathString
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let keyPathString: KeyPathString
|
||||
|
||||
internal init(keyPathString: KeyPathString) {
|
||||
|
||||
self.keyPathString = keyPathString
|
||||
}
|
||||
|
||||
internal func appending<D2>(keyPathString: KeyPathString) -> DynamicObjectMeta<(R, D), D2> {
|
||||
|
||||
return .init(keyPathString: [self.keyPathString, keyPathString].joined(separator: "."))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DynamicObjectMeta where Destination: NSManagedObject
|
||||
|
||||
extension DynamicObjectMeta where Destination: NSManagedObject {
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V: AllowedObjectiveCAttributeKeyPathValue>(dynamicMember member: KeyPath<Destination, V>) -> DynamicObjectMeta<(Root, Destination), V.ReturnValueType> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V: NSManagedObject>(dynamicMember member: KeyPath<Destination, V>) -> DynamicObjectMeta<(Root, Destination), V> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V: NSManagedObject>(dynamicMember member: KeyPath<Destination, V?>) -> DynamicObjectMeta<(Root, Destination), V> {
|
||||
|
||||
// TODO: not working
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V: NSOrderedSet>(dynamicMember member: KeyPath<Destination, V>) -> DynamicObjectMeta<(Root, Destination), V> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V: NSOrderedSet>(dynamicMember member: KeyPath<Destination, V?>) -> DynamicObjectMeta<(Root, Destination), V> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V: NSSet>(dynamicMember member: KeyPath<Destination, V>) -> DynamicObjectMeta<(Root, Destination), V> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V: NSSet>(dynamicMember member: KeyPath<Destination, V?>) -> DynamicObjectMeta<(Root, Destination), V> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DynamicObjectMeta where Destination: CoreStoreObject
|
||||
|
||||
extension DynamicObjectMeta where Destination: CoreStoreObject {
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<K: AttributeKeyPathStringConvertible>(dynamicMember member: KeyPath<Destination, K>) -> DynamicObjectMeta<(Root, Destination), K.ReturnValueType> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<K: RelationshipKeyPathStringConvertible>(dynamicMember member: KeyPath<Destination, K>) -> DynamicObjectMeta<(Root, Destination), K.DestinationValueType> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
305
Sources/Field.Computed.swift
Normal file
305
Sources/Field.Computed.swift
Normal file
@@ -0,0 +1,305 @@
|
||||
//
|
||||
// Field.Computed.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 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: - FieldContainer
|
||||
|
||||
extension FieldContainer {
|
||||
|
||||
// MARK: - Computed
|
||||
|
||||
/**
|
||||
The containing type for computed property values. Any type that conforms to `FieldStorableType` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
@Field.Stored("species")
|
||||
var species = ""
|
||||
|
||||
@Field.Computed("pluralName", customGetter: Animal.pluralName(_:))
|
||||
var pluralName: String = ""
|
||||
|
||||
@Field.PlistCoded("color")
|
||||
var color: UIColor?
|
||||
}
|
||||
```
|
||||
- Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.Computed(...) var` syntax will be ignored.
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct Computed<V>: AttributeKeyPathStringConvertible, FieldAttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
@Field.Stored("title")
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Computed("displayName", customGetter: Person.getName(_:))
|
||||
var displayName: String = ""
|
||||
|
||||
private static func getName(_ partialObject: PartialObject<Person>) -> String {
|
||||
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
|
||||
if !cachedDisplayName.isEmpty {
|
||||
return cachedDisplayName
|
||||
}
|
||||
let title = partialObject.value(for: \.$title)
|
||||
let name = partialObject.value(for: \.$name)
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.value(for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
renamingIdentifier: renamingIdentifier,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Overload for compiler message only
|
||||
*/
|
||||
@available(*, unavailable, message: "Field.Computed properties are not allowed to have default values.")
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
|
||||
fatalError()
|
||||
}
|
||||
|
||||
|
||||
// MARK: @propertyWrapper
|
||||
|
||||
@available(*, unavailable)
|
||||
public var wrappedValue: V {
|
||||
|
||||
get { fatalError() }
|
||||
set { fatalError() }
|
||||
}
|
||||
|
||||
public var projectedValue: Self {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
public static subscript(
|
||||
_enclosingInstance instance: O,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<O, V>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<O, Self>
|
||||
) -> V {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
instance.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
instance.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return self.modify(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, newValue: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: FieldProtocol
|
||||
|
||||
internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? {
|
||||
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
let field = field as! Self
|
||||
if let customGetter = field.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(rawObject))
|
||||
}
|
||||
let keyPath = field.keyPath
|
||||
switch rawObject.value(forKey: keyPath) {
|
||||
|
||||
case let rawValue as V:
|
||||
return rawValue
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) {
|
||||
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
rawObject.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
let newValue = newValue as! V
|
||||
let field = field as! Self
|
||||
let keyPath = field.keyPath
|
||||
if let customSetter = field.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(rawObject), newValue)
|
||||
}
|
||||
return rawObject.setValue(newValue, forKey: keyPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: FieldAttributeProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> FieldAttributeProtocol.EntityDescriptionValues
|
||||
|
||||
internal var getter: CoreStoreManagedObject.CustomGetter? {
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
return customGetter(PartialObject<O>(rawObject))
|
||||
}
|
||||
}
|
||||
|
||||
internal var setter: CoreStoreManagedObject.CustomSetter? {
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
return customSetter(PartialObject<O>(rawObject), newValue as! V)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init(
|
||||
keyPath: KeyPathString,
|
||||
isOptional: Bool,
|
||||
renamingIdentifier: @escaping () -> String?,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)?,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? ,
|
||||
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
attributeType: .undefinedAttributeType,
|
||||
isOptional: isOptional,
|
||||
isTransient: true,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: nil,
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: nil
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
|
||||
}
|
||||
}
|
||||
9
Sources/Field.PlistCoded.swift
Normal file
9
Sources/Field.PlistCoded.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Field.PlistCoded.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Created by John Estropia on 2020/01/15.
|
||||
// Copyright © 2020 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
362
Sources/Field.Stored.swift
Normal file
362
Sources/Field.Stored.swift
Normal file
@@ -0,0 +1,362 @@
|
||||
//
|
||||
// Field.Stored.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 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: - FieldContainer
|
||||
|
||||
extension FieldContainer {
|
||||
|
||||
// MARK: - Stored
|
||||
|
||||
/**
|
||||
The containing type for stored property values. Any type that conforms to `FieldStorableType` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
@Field.Stored("species")
|
||||
var species = ""
|
||||
|
||||
@Field.Computed("pluralName", customGetter: Animal.pluralName(_:))
|
||||
var pluralName: String = ""
|
||||
|
||||
@Field.PlistCoded("color")
|
||||
var color: UIColor?
|
||||
}
|
||||
```
|
||||
- Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.Stored(...) var` syntax will be ignored.
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct Stored<V: FieldStorableType>: AttributeKeyPathStringConvertible, FieldAttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
@Field.Stored("title")
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Computed("displayName", customGetter: Person.getName(_:))
|
||||
var displayName: String = ""
|
||||
|
||||
private static func getName(_ partialObject: PartialObject<Person>) -> String {
|
||||
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
|
||||
if !cachedDisplayName.isEmpty {
|
||||
return cachedDisplayName
|
||||
}
|
||||
let title = partialObject.value(for: \.$title)
|
||||
let name = partialObject.value(for: \.$name)
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first create
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.value(for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
|
||||
self.init(
|
||||
wrappedValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: renamingIdentifier,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: @propertyWrapper
|
||||
|
||||
@available(*, unavailable)
|
||||
public var wrappedValue: V {
|
||||
|
||||
get { fatalError() }
|
||||
set { fatalError() }
|
||||
}
|
||||
|
||||
public var projectedValue: Self {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
public static subscript(
|
||||
_enclosingInstance instance: O,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<O, V>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<O, Self>
|
||||
) -> V {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
instance.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
instance.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return self.modify(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, newValue: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: FieldProtocol
|
||||
|
||||
internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? {
|
||||
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
let field = field as! Self
|
||||
if let customGetter = field.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(rawObject))
|
||||
}
|
||||
let keyPath = field.keyPath
|
||||
guard case let rawValue as V.FieldStoredNativeType = rawObject.value(forKey: keyPath) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return V.cs_fromFieldStoredNativeType(rawValue)
|
||||
}
|
||||
|
||||
internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) {
|
||||
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
rawObject.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
let newValue = newValue as! V
|
||||
let field = field as! Self
|
||||
let keyPath = field.keyPath
|
||||
if let customSetter = field.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(rawObject), newValue)
|
||||
}
|
||||
return rawObject.setValue(
|
||||
newValue.cs_toFieldStoredNativeType(),
|
||||
forKey: keyPath
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: FieldAttributeProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> FieldAttributeProtocol.EntityDescriptionValues
|
||||
|
||||
internal var getter: CoreStoreManagedObject.CustomGetter? {
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value.cs_toFieldStoredNativeType()
|
||||
}
|
||||
}
|
||||
|
||||
internal var setter: CoreStoreManagedObject.CustomSetter? {
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
V.cs_fromFieldStoredNativeType(newValue as! V.FieldStoredNativeType)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init(
|
||||
wrappedValue initial: @escaping () -> V,
|
||||
keyPath: KeyPathString,
|
||||
isOptional: Bool,
|
||||
versionHashModifier: @escaping () -> String?,
|
||||
renamingIdentifier: @escaping () -> String?,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)?,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? ,
|
||||
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
attributeType: V.cs_rawAttributeType,
|
||||
isOptional: isOptional,
|
||||
isTransient: false,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: initial().cs_toFieldStoredNativeType()
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Stored where V: FieldOptionalType
|
||||
|
||||
extension FieldContainer.Stored where V: FieldOptionalType {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
@Field.Stored("title")
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Computed("displayName", customGetter: Person.getName(_:))
|
||||
var displayName: String = ""
|
||||
|
||||
private static func getName(_ partialObject: PartialObject<Person>) -> String {
|
||||
let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName)
|
||||
if !cachedDisplayName.isEmpty {
|
||||
return cachedDisplayName
|
||||
}
|
||||
let title = partialObject.value(for: \.$title)
|
||||
let name = partialObject.value(for: \.$name)
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first create
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.value(for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V = nil,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
|
||||
self.init(
|
||||
wrappedValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: true,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: renamingIdentifier,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
71
Sources/Field.swift
Normal file
71
Sources/Field.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Field.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 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: - DynamicObject
|
||||
|
||||
extension DynamicObject where Self: CoreStoreObject {
|
||||
|
||||
/**
|
||||
The containing type for value propertiess. `Field` properties support any type that conforms to `ImportableAttributeType`.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
@Field.Stored("species")
|
||||
var species = ""
|
||||
|
||||
@Field.Stored("nickname")
|
||||
var nickname: String?
|
||||
|
||||
@Field.PlistCoded("color")
|
||||
var color: UIColor?
|
||||
}
|
||||
```
|
||||
- Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.*(...) var` syntax will be ignored.
|
||||
*/
|
||||
public typealias Field = FieldContainer<Self>
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer
|
||||
|
||||
/**
|
||||
The containing type for value properties. Use the `DynamicObject.Field` typealias instead for shorter syntax.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
@Field.Stored("species")
|
||||
var species = ""
|
||||
|
||||
@Field.Stored("nickname")
|
||||
var nickname: String?
|
||||
|
||||
@Field.PlistCoded("color")
|
||||
var color: UIColor?
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum FieldContainer<O: CoreStoreObject> {}
|
||||
56
Sources/FieldOptionalType.swift
Normal file
56
Sources/FieldOptionalType.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// FieldOptionalType.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 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: - FieldOptionalType
|
||||
|
||||
public protocol FieldOptionalType: ExpressibleByNilLiteral {
|
||||
|
||||
/**
|
||||
The type for the wrapped value
|
||||
*/
|
||||
associatedtype Wrapped
|
||||
|
||||
/**
|
||||
The wrapped value
|
||||
*/
|
||||
var cs_wrappedValue: Wrapped? { get }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Optional: FieldOptionalType
|
||||
|
||||
extension Optional: FieldOptionalType {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@inlinable
|
||||
public var cs_wrappedValue: Wrapped? {
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
57
Sources/FieldProtocol.swift
Normal file
57
Sources/FieldProtocol.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// FieldProtocol.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 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: - FieldProtocol
|
||||
|
||||
internal protocol FieldProtocol: PropertyProtocol {
|
||||
|
||||
static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any?
|
||||
static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldAttributeProtocol
|
||||
|
||||
internal protocol FieldAttributeProtocol: FieldProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
attributeType: NSAttributeType,
|
||||
isOptional: Bool,
|
||||
isTransient: Bool,
|
||||
allowsExternalBinaryDataStorage: Bool,
|
||||
versionHashModifier: String?,
|
||||
renamingIdentifier: String?,
|
||||
affectedByKeyPaths: Set<KeyPathString>,
|
||||
defaultValue: Any?
|
||||
)
|
||||
|
||||
var entityDescriptionValues: () -> EntityDescriptionValues { get }
|
||||
var getter: CoreStoreManagedObject.CustomGetter? { get }
|
||||
var setter: CoreStoreManagedObject.CustomSetter? { get }
|
||||
}
|
||||
259
Sources/FieldStorableType.swift
Normal file
259
Sources/FieldStorableType.swift
Normal file
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// FieldStorableType.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
|
||||
// MARK: - FieldStorableType
|
||||
|
||||
public protocol FieldStorableType {
|
||||
|
||||
/**
|
||||
The `NSAttributeType` for this type
|
||||
*/
|
||||
associatedtype FieldStoredNativeType
|
||||
|
||||
/**
|
||||
The `NSAttributeType` for this type
|
||||
*/
|
||||
static var cs_rawAttributeType: NSAttributeType { get }
|
||||
|
||||
/**
|
||||
Creates an instance of this type from raw native value.
|
||||
*/
|
||||
@inline(__always)
|
||||
static func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self
|
||||
|
||||
/**
|
||||
Creates `FieldStoredNativeType` value from this instance.
|
||||
*/
|
||||
@inline(__always)
|
||||
func cs_toFieldStoredNativeType() -> Any?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldStorableType where Self: ImportableAttributeType, FieldStoredNativeType == QueryableNativeType
|
||||
|
||||
extension FieldStorableType where Self: ImportableAttributeType, FieldStoredNativeType == QueryableNativeType {
|
||||
|
||||
@inline(__always)
|
||||
public static func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self {
|
||||
|
||||
return self.cs_fromQueryableNativeType(value)!
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public func cs_toFieldStoredNativeType() -> Any? {
|
||||
|
||||
return self.cs_toQueryableNativeType()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Bool
|
||||
|
||||
extension Bool: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - CGFloat
|
||||
|
||||
extension CGFloat: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Data
|
||||
|
||||
extension Data: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Date
|
||||
|
||||
extension Date: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Double
|
||||
|
||||
extension Double: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Float
|
||||
|
||||
extension Float: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Int
|
||||
|
||||
extension Int: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Int8
|
||||
|
||||
extension Int8: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Int16
|
||||
|
||||
extension Int16: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Int32
|
||||
|
||||
extension Int32: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Int64
|
||||
|
||||
extension Int64: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - NSData
|
||||
|
||||
extension NSData: FieldStorableType {
|
||||
|
||||
@nonobjc @inline(__always)
|
||||
public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self {
|
||||
|
||||
return self.cs_fromQueryableNativeType(value)!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSDate
|
||||
|
||||
extension NSDate: FieldStorableType {
|
||||
|
||||
@nonobjc @inline(__always)
|
||||
public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self {
|
||||
|
||||
return self.cs_fromQueryableNativeType(value)!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSNumber
|
||||
|
||||
extension NSNumber: FieldStorableType {
|
||||
|
||||
@nonobjc @inline(__always)
|
||||
public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self {
|
||||
|
||||
return self.cs_fromQueryableNativeType(value)!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSString
|
||||
|
||||
extension NSString: FieldStorableType {
|
||||
|
||||
@nonobjc @inline(__always)
|
||||
public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self {
|
||||
|
||||
return self.cs_fromQueryableNativeType(value)!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSURL
|
||||
|
||||
extension NSURL: FieldStorableType {
|
||||
|
||||
@nonobjc @inline(__always)
|
||||
public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self {
|
||||
|
||||
return self.cs_fromQueryableNativeType(value)!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSUUID
|
||||
|
||||
extension NSUUID: FieldStorableType {
|
||||
|
||||
@nonobjc @inline(__always)
|
||||
public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self {
|
||||
|
||||
return self.cs_fromQueryableNativeType(value)!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - String
|
||||
|
||||
extension String: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - URL
|
||||
|
||||
extension URL: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - UUID
|
||||
|
||||
extension UUID: FieldStorableType {}
|
||||
|
||||
|
||||
// MARK: - Optional<FieldStorableType>
|
||||
|
||||
extension Optional: FieldStorableType where Wrapped: FieldStorableType {
|
||||
|
||||
// MARK: FieldStorableType
|
||||
|
||||
public typealias FieldStoredNativeType = Wrapped.FieldStoredNativeType?
|
||||
|
||||
public static var cs_rawAttributeType: NSAttributeType {
|
||||
|
||||
return Wrapped.cs_rawAttributeType
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public static func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self {
|
||||
|
||||
switch value {
|
||||
|
||||
case nil,
|
||||
is NSNull:
|
||||
return nil
|
||||
|
||||
case let value?:
|
||||
return Wrapped.cs_fromFieldStoredNativeType(value)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public func cs_toFieldStoredNativeType() -> Any? {
|
||||
|
||||
switch self {
|
||||
|
||||
case nil,
|
||||
is NSNull:
|
||||
return nil
|
||||
|
||||
case let value?:
|
||||
return value.cs_toFieldStoredNativeType()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,6 +359,20 @@ public func ~= <O: NSManagedObject, D: NSManagedObject, S: Sequence>(_ sequence:
|
||||
}
|
||||
|
||||
|
||||
// MARK: - KeyPath where Root: CoreStoreObject, Value: FieldContainer<Root>.Stored<QueryableAttributeType & Equatable>
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.nickname == "John"))
|
||||
```
|
||||
*/
|
||||
public func == <O, V>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
|
||||
|
||||
return Where<O>(keyPath, isEqualTo: value)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - KeyPath where Root: CoreStoreObject, Value: ValueContainer<Root>.Required<QueryableAttributeType & Equatable>
|
||||
|
||||
/**
|
||||
|
||||
@@ -106,47 +106,47 @@ extension Int64: AllowedObjectiveCKeyPathValue {
|
||||
|
||||
extension NSData: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSData
|
||||
}
|
||||
|
||||
extension NSDate: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSDate
|
||||
}
|
||||
|
||||
extension NSManagedObject: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSManagedObject
|
||||
}
|
||||
|
||||
extension NSNumber: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSNumber
|
||||
}
|
||||
|
||||
extension NSString: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSString
|
||||
}
|
||||
|
||||
extension NSSet: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSSet
|
||||
}
|
||||
|
||||
extension NSOrderedSet: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSOrderedSet
|
||||
}
|
||||
|
||||
extension NSURL: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSURL
|
||||
}
|
||||
|
||||
extension NSUUID: AllowedOptionalObjectiveCKeyPathValue {
|
||||
|
||||
public typealias DestinationValueType = Self
|
||||
public typealias DestinationValueType = NSUUID
|
||||
}
|
||||
|
||||
extension String: AllowedOptionalObjectiveCKeyPathValue {
|
||||
@@ -240,32 +240,32 @@ extension Int64: AllowedObjectiveCAttributeKeyPathValue {
|
||||
|
||||
extension NSData: AllowedObjectiveCAttributeKeyPathValue {
|
||||
|
||||
public typealias ReturnValueType = Self
|
||||
public typealias ReturnValueType = NSData
|
||||
}
|
||||
|
||||
extension NSDate: AllowedObjectiveCAttributeKeyPathValue {
|
||||
|
||||
public typealias ReturnValueType = Self
|
||||
public typealias ReturnValueType = NSDate
|
||||
}
|
||||
|
||||
extension NSNumber: AllowedObjectiveCAttributeKeyPathValue {
|
||||
|
||||
public typealias ReturnValueType = Self
|
||||
public typealias ReturnValueType = NSNumber
|
||||
}
|
||||
|
||||
extension NSString: AllowedObjectiveCAttributeKeyPathValue {
|
||||
|
||||
public typealias ReturnValueType = Self
|
||||
public typealias ReturnValueType = NSString
|
||||
}
|
||||
|
||||
extension NSURL: AllowedObjectiveCAttributeKeyPathValue {
|
||||
|
||||
public typealias ReturnValueType = Self
|
||||
public typealias ReturnValueType = NSURL
|
||||
}
|
||||
|
||||
extension NSUUID: AllowedObjectiveCAttributeKeyPathValue {
|
||||
|
||||
public typealias ReturnValueType = Self
|
||||
public typealias ReturnValueType = NSUUID
|
||||
}
|
||||
|
||||
extension String: AllowedObjectiveCAttributeKeyPathValue {
|
||||
|
||||
@@ -367,6 +367,36 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Stored<V>>) -> V? {
|
||||
|
||||
guard
|
||||
let object = self.object,
|
||||
let rawObject = object.rawObject
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return FieldContainer<OBase>.Stored<V>.read(field: object[keyPath: member], for: rawObject) as! V?
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Computed<V>>) -> V? {
|
||||
|
||||
guard
|
||||
let object = self.object,
|
||||
let rawObject = object.rawObject
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return FieldContainer<OBase>.Computed<V>.read(field: object[keyPath: member], for: rawObject) as! V?
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
|
||||
@@ -175,6 +175,40 @@ extension ObjectSnapshot where O: NSManagedObject {
|
||||
|
||||
extension ObjectSnapshot where O: CoreStoreObject {
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Stored<V>>) -> V {
|
||||
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
return self.values[key] as! V
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Computed<V>>) -> V {
|
||||
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
return self.values[key] as! V
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
|
||||
@@ -41,6 +41,89 @@ public struct PartialObject<O: CoreStoreObject> {
|
||||
|
||||
return O.cs_fromRaw(object: self.rawObject)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Field.Stored accessors/mutators
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public func value<V>(for property: (O) -> FieldContainer<O>.Stored<V>) -> V {
|
||||
|
||||
return V.cs_fromFieldStoredNativeType(
|
||||
self.rawObject.value(forKey: property(O.meta).keyPath) as! V.FieldStoredNativeType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public func value<V>(for property: (O) -> FieldContainer<O>.Computed<V>) -> V {
|
||||
|
||||
switch self.rawObject.value(forKey: property(O.meta).keyPath) {
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
default:
|
||||
return nil as Any? as! V // filter NSNull
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the specified property from the managed object’s private internal storage.
|
||||
|
||||
This method does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily by subclasses that implement custom accessor methods that need direct access to the receiver’s private storage.
|
||||
*/
|
||||
public func primitiveValue<V>(for property: (O) -> FieldContainer<O>.Stored<V>) -> V {
|
||||
|
||||
return V.cs_fromFieldStoredNativeType(
|
||||
self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V.FieldStoredNativeType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the specified property from the managed object’s private internal storage.
|
||||
|
||||
This method does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily by subclasses that implement custom accessor methods that need direct access to the receiver’s private storage.
|
||||
*/
|
||||
public func primitiveValue<V>(for property: (O) -> FieldContainer<O>.Computed<V>) -> V {
|
||||
|
||||
switch self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) {
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
default:
|
||||
return nil as Any? as! V // filter NSNull
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sets in the object's private internal storage the value of a given property.
|
||||
|
||||
Sets in the receiver’s private internal storage the value of the property specified by key to value.
|
||||
*/
|
||||
public func setPrimitiveValue<V>(_ value: V, for property: (O) -> FieldContainer<O>.Stored<V>) {
|
||||
|
||||
self.rawObject.setPrimitiveValue(
|
||||
value.cs_toFieldStoredNativeType(),
|
||||
forKey: property(O.meta).keyPath
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Sets in the object's private internal storage the value of a given property.
|
||||
|
||||
Sets in the receiver’s private internal storage the value of the property specified by key to value.
|
||||
*/
|
||||
public func setPrimitiveValue<V>(_ value: V, for property: (O) -> FieldContainer<O>.Computed<V>) {
|
||||
|
||||
self.rawObject.setPrimitiveValue(
|
||||
value,
|
||||
forKey: property(O.meta).keyPath
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Value.Required accessors/mutators
|
||||
|
||||
@@ -29,7 +29,7 @@ import CoreData
|
||||
|
||||
// MARK: - PropertyProtocol
|
||||
|
||||
internal protocol PropertyProtocol: AnyObject {
|
||||
internal protocol PropertyProtocol {
|
||||
|
||||
var keyPath: KeyPathString { get }
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import CoreData
|
||||
|
||||
// MARK: - RelationshipProtocol
|
||||
|
||||
internal protocol RelationshipProtocol: PropertyProtocol {
|
||||
internal protocol RelationshipProtocol: AnyObject, PropertyProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
isToMany: Bool,
|
||||
|
||||
@@ -73,22 +73,22 @@ public struct LocalStorageOptions: OptionSet, ExpressibleByNilLiteral {
|
||||
/**
|
||||
Tells the `DataStack` that the store should not be migrated or recreated, and should simply fail on model mismatch
|
||||
*/
|
||||
public static let none = LocalStorageOptions(rawValue: 0)
|
||||
public static let none: LocalStorageOptions = []
|
||||
|
||||
/**
|
||||
Tells the `DataStack` to delete and recreate the store on model mismatch, otherwise exceptions will be thrown on failure instead
|
||||
*/
|
||||
public static let recreateStoreOnModelMismatch = LocalStorageOptions(rawValue: 1 << 0)
|
||||
public static let recreateStoreOnModelMismatch: LocalStorageOptions = .init(rawValue: 1 << 0)
|
||||
|
||||
/**
|
||||
Tells the `DataStack` to prevent progressive migrations for the store
|
||||
*/
|
||||
public static let preventProgressiveMigration = LocalStorageOptions(rawValue: 1 << 1)
|
||||
public static let preventProgressiveMigration: LocalStorageOptions = .init(rawValue: 1 << 1)
|
||||
|
||||
/**
|
||||
Tells the `DataStack` to allow lightweight migration for the store when added synchronously
|
||||
*/
|
||||
public static let allowSynchronousLightweightMigration = LocalStorageOptions(rawValue: 1 << 2)
|
||||
public static let allowSynchronousLightweightMigration: LocalStorageOptions = .init(rawValue: 1 << 2)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -110,13 +110,8 @@ public struct VersionLock: ExpressibleByDictionaryLiteral, Equatable {
|
||||
|
||||
var hashesByEntityName: [EntityName: Data] = [:]
|
||||
for (entityName, intArray) in keyValues {
|
||||
|
||||
hashesByEntityName[entityName] = Data(
|
||||
buffer: UnsafeBufferPointer(
|
||||
start: intArray,
|
||||
count: intArray.count
|
||||
)
|
||||
)
|
||||
|
||||
hashesByEntityName[entityName] = intArray.withUnsafeBufferPointer(Data.init(buffer:))
|
||||
}
|
||||
self.hashesByEntityName = hashesByEntityName
|
||||
}
|
||||
|
||||
@@ -174,21 +174,41 @@ public struct Where<O: DynamicObject>: WhereClauseType, FetchClause, QueryClause
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) == nil"))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares equality
|
||||
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter value: the arguments for the `==` operator
|
||||
*/
|
||||
public init<U: QueryableAttributeType>(_ keyPath: KeyPathString, isEqualTo value: U?) {
|
||||
|
||||
public init<V: FieldStorableType>(_ keyPath: KeyPathString, isEqualTo value: V) {
|
||||
|
||||
switch value {
|
||||
|
||||
|
||||
case nil,
|
||||
is NSNull:
|
||||
self.init(NSPredicate(format: "\(keyPath) == nil"))
|
||||
|
||||
|
||||
case let value:
|
||||
self.init(NSPredicate(format: "\(keyPath) == %@", argumentArray: [value.cs_toFieldStoredNativeType() as Any]))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares equality
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter value: the arguments for the `==` operator
|
||||
*/
|
||||
@_disfavoredOverload
|
||||
public init<U: QueryableAttributeType>(_ keyPath: KeyPathString, isEqualTo value: U?) {
|
||||
|
||||
switch value {
|
||||
|
||||
case nil,
|
||||
is NSNull:
|
||||
self.init(NSPredicate(format: "\(keyPath) == nil"))
|
||||
|
||||
case let value?:
|
||||
self.init(NSPredicate(format: "\(keyPath) == %@", argumentArray: [value.cs_toQueryableNativeType()]))
|
||||
}
|
||||
@@ -222,6 +242,17 @@ public struct Where<O: DynamicObject>: WhereClauseType, FetchClause, QueryClause
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) == %@", argumentArray: [objectID]))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares membership
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter list: the sequence to check membership of
|
||||
*/
|
||||
public init<S: Sequence>(_ keyPath: KeyPathString, isMemberOf list: S) where S.Iterator.Element: FieldStorableType {
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) IN %@", list.map({ $0.cs_toFieldStoredNativeType() }) as NSArray))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares membership
|
||||
@@ -229,6 +260,7 @@ public struct Where<O: DynamicObject>: WhereClauseType, FetchClause, QueryClause
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter list: the sequence to check membership of
|
||||
*/
|
||||
@_disfavoredOverload
|
||||
public init<S: Sequence>(_ keyPath: KeyPathString, isMemberOf list: S) where S.Iterator.Element: QueryableAttributeType {
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) IN %@", list.map({ $0.cs_toQueryableNativeType() }) as NSArray))
|
||||
@@ -408,6 +440,17 @@ extension Where where O: NSManagedObject {
|
||||
// MARK: - Where where O: CoreStoreObject
|
||||
|
||||
extension Where where O: CoreStoreObject {
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares equality
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter value: the arguments for the `==` operator
|
||||
*/
|
||||
public init<V>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, isEqualTo value: V) {
|
||||
|
||||
self.init(O.meta[keyPath: keyPath].keyPath, isEqualTo: value)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares equality to `nil`
|
||||
|
||||
Reference in New Issue
Block a user