prototype new Fields as propertyWrappers (Swift 5.2 above only)

This commit is contained in:
John Estropia
2020-01-15 18:29:58 +09:00
parent 5e37ee4566
commit 43f61359da
33 changed files with 1796 additions and 337 deletions

View File

@@ -29,7 +29,7 @@ import CoreData
// MARK: - AttributeProtocol
internal protocol AttributeProtocol: PropertyProtocol {
internal protocol AttributeProtocol: AnyObject, PropertyProtocol {
typealias EntityDescriptionValues = (
attributeType: NSAttributeType,

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
/**

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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

View 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)?
}
}

View 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
View 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
View 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> {}

View 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
}
}

View 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 }
}

View 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()
}
}
}

View File

@@ -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>
/**

View File

@@ -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 {

View File

@@ -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.
*/

View File

@@ -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.
*/

View File

@@ -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 objects 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 receivers 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 objects 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 receivers 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 receivers 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 receivers 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

View File

@@ -29,7 +29,7 @@ import CoreData
// MARK: - PropertyProtocol
internal protocol PropertyProtocol: AnyObject {
internal protocol PropertyProtocol {
var keyPath: KeyPathString { get }
}

View File

@@ -29,7 +29,7 @@ import CoreData
// MARK: - RelationshipProtocol
internal protocol RelationshipProtocol: PropertyProtocol {
internal protocol RelationshipProtocol: AnyObject, PropertyProtocol {
typealias EntityDescriptionValues = (
isToMany: Bool,

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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`