From 431ce35057f47ee6097f73fcf8758f51586470fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20He=C3=9F?= Date: Thu, 15 Feb 2024 17:42:12 +0100 Subject: [PATCH 1/2] Fix building of tests on iOS ... Macros tests don't run there. --- Tests/ManagedModelMacrosTests/ManagedModelMacrosTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/ManagedModelMacrosTests/ManagedModelMacrosTests.swift b/Tests/ManagedModelMacrosTests/ManagedModelMacrosTests.swift index a2f0e16..972ada8 100644 --- a/Tests/ManagedModelMacrosTests/ManagedModelMacrosTests.swift +++ b/Tests/ManagedModelMacrosTests/ManagedModelMacrosTests.swift @@ -371,6 +371,7 @@ final class ModelMacroTests: XCTestCase { // MARK: - Helper +#if canImport(ManagedModelMacros) func parseAndExplode(_ source: String) -> Syntax { // Parse the original source file. let sourceFile : SourceFileSyntax = Parser.parse(source: source) @@ -393,6 +394,7 @@ final class ModelMacroTests: XCTestCase { return explodedFile } +#endif // canImport(ManagedModelMacros) // Note: This does not fail the test, but it does fail the compiler. // https://github.com/Data-swift/ManagedModels/issues/18 From 28d5984be4d13915b2e1a249c09f2b517da8f373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20He=C3=9F?= Date: Thu, 15 Feb 2024 17:46:08 +0100 Subject: [PATCH 2/2] Drop `CodableBox`, not required? Directly store the Codable Swift value in the property as an Any. Disadvantage: We cannot set the `transformedValueClass` in the transformer nor the `attributeValueClassName` in the `Attribute` (which will stick to `Any`. But that doesn't seem to confused CoreData. --- .../PersistentModel/PersistentModel+KVC.swift | 21 ++--- .../SchemaCompatibility/CodableBox.swift | 86 ------------------- .../CodableTransformer.swift | 48 +++++++++++ .../NSAttributeDescription+Data.swift | 16 ++-- .../CodablePropertiesTest.swift | 12 +-- 5 files changed, 70 insertions(+), 113 deletions(-) delete mode 100644 Sources/ManagedModels/SchemaCompatibility/CodableBox.swift create mode 100644 Sources/ManagedModels/SchemaCompatibility/CodableTransformer.swift diff --git a/Sources/ManagedModels/PersistentModel/PersistentModel+KVC.swift b/Sources/ManagedModels/PersistentModel/PersistentModel+KVC.swift index c83eb0d..5ba24d8 100644 --- a/Sources/ManagedModels/PersistentModel/PersistentModel+KVC.swift +++ b/Sources/ManagedModels/PersistentModel/PersistentModel+KVC.swift @@ -232,14 +232,14 @@ public extension PersistentModel { func setValue(forKey key: String, to value: T) where T: Codable { willChangeValue(forKey: key); defer { didChangeValue(forKey: key) } - setPrimitiveValue(CodableBox(value), forKey: key) + setPrimitiveValue(value, forKey: key) } func setValue(forKey key: String, to value: T) where T: Codable & AnyOptional { willChangeValue(forKey: key); defer { didChangeValue(forKey: key) } - if value.isSome { setPrimitiveValue(CodableBox(value), forKey: key) } + if value.isSome { setPrimitiveValue(value, forKey: key) } else { setPrimitiveValue(nil, forKey: key) } } @@ -249,10 +249,7 @@ public extension PersistentModel { fatalError("No box found for non-optional Codable value for key \(key)?") } - if let box = value as? CodableBox { - guard let value = box.value else { - fatalError("Box has no value for non-optional Codable for key \(key)?") - } + if let value = value as? T { return value } @@ -266,17 +263,13 @@ public extension PersistentModel { } } - guard let value = value as? T else { - fatalError("Unexpected value for key \(key)? \(value)") - } - assertionFailure("Codable value is directly stored? \(value)") - return value + fatalError("Codable value type doesn't match? \(value)") } func getValue(forKey key: String) -> T where T: Codable & AnyOptional { willAccessValue(forKey: key); defer { didAccessValue(forKey: key) } guard let value = primitiveValue(forKey: key) else { return .noneValue } - if let box = value as? CodableBox { return box.value ?? .noneValue } + if let value = value as? T { return value } if let data = value as? Data { assertionFailure("Unexpected Data as primitive!") @@ -291,7 +284,7 @@ public extension PersistentModel { guard let value = value as? T else { fatalError("Unexpected value for key \(key)? \(value)") } - assertionFailure("Codable value is directly stored? \(value)") - return value + assertionFailure("Codable value type doesn't match? \(value)") + return .noneValue } } diff --git a/Sources/ManagedModels/SchemaCompatibility/CodableBox.swift b/Sources/ManagedModels/SchemaCompatibility/CodableBox.swift deleted file mode 100644 index fb45a73..0000000 --- a/Sources/ManagedModels/SchemaCompatibility/CodableBox.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// Created by Helge Heß. -// Copyright © 2023 ZeeZide GmbH. -// - -import Foundation -import CoreData - -/** - * Helper object to store arbitrary Codable Swift types in a CoreData - * property. - */ -final class CodableBox: NSObject, NSCopying { - // Inspired by @radianttap - - var value : T? - - init(_ value: T) { self.value = value } - - private init(_ value: T?) { self.value = value } - - private init?(data: Data) { - do { - value = try JSONDecoder().decode(T.self, from: data) - } - catch { - assertionFailure("Could not decode JSON value of property? \(error)") - value = nil - } - } - - func copy(with zone: NSZone? = nil) -> Any { CodableBox(self.value) } - - var data : Data? { - set { - guard let data = newValue else { - value = nil - return - } - do { - value = try JSONDecoder().decode(T.self, from: data) - } - catch { - assertionFailure("Could not decode JSON value of property? \(error)") - value = nil - } - } - get { - guard let value else { return nil } - do { - return try JSONEncoder().encode(value) - } - catch { - assertionFailure("Could not encode JSON value of property? \(error)") - return nil - } - } - } - - final class Transformer: ValueTransformer { - - override class func transformedValueClass() -> AnyClass { - CodableBox.self - } - override class func allowsReverseTransformation() -> Bool { true } - - override func transformedValue(_ value: Any?) -> Any? { - // value is the box - guard var value else { return nil } - if let baseTyped = value as? T { - value = CodableBox(baseTyped) - } - guard let typed = value as? CodableBox else { - assertionFailure("Value to be transformed is not the box? \(value)") - return nil - } - return typed.data - } - - override func reverseTransformedValue(_ value: Any?) -> Any? { - guard let value else { return nil } - guard let data = value as? Data else { return nil } - return CodableBox(data: data)?.value - } - } -} diff --git a/Sources/ManagedModels/SchemaCompatibility/CodableTransformer.swift b/Sources/ManagedModels/SchemaCompatibility/CodableTransformer.swift new file mode 100644 index 0000000..19e68ce --- /dev/null +++ b/Sources/ManagedModels/SchemaCompatibility/CodableTransformer.swift @@ -0,0 +1,48 @@ +// +// Created by Helge Heß. +// Copyright © 2023 ZeeZide GmbH. +// + +import Foundation +import CoreData + +final class CodableTransformer: ValueTransformer { + + #if false + override class func transformedValueClass() -> AnyClass { + T.self // doesn't work + } + #endif + override class func allowsReverseTransformation() -> Bool { true } + + override func transformedValue(_ value: Any?) -> Any? { + // value is the box + guard let value else { return nil } + guard let typed = value as? T else { + assertionFailure("Value to be transformed is not the right type? \(value)") + return nil + } + do { + return try JSONEncoder().encode(typed) + } + catch { + assertionFailure("Could not encode JSON value of property? \(error)") + return nil + } + } + + override func reverseTransformedValue(_ value: Any?) -> Any? { + guard let value else { return nil } + guard let data = value as? Data else { + assert(value is Data, "Reverse value is not `Data`?") + return nil + } + do { + return try JSONDecoder().decode(T.self, from: data) + } + catch { + assertionFailure("Could not decode JSON value of property? \(error)") + return nil + } + } +} diff --git a/Sources/ManagedModels/SchemaCompatibility/NSAttributeDescription+Data.swift b/Sources/ManagedModels/SchemaCompatibility/NSAttributeDescription+Data.swift index 7241cc2..1e8c399 100644 --- a/Sources/ManagedModels/SchemaCompatibility/NSAttributeDescription+Data.swift +++ b/Sources/ManagedModels/SchemaCompatibility/NSAttributeDescription+Data.swift @@ -40,8 +40,8 @@ extension CoreData.NSAttributeDescription: SchemaProperty { if let primitiveType = newValue as? CoreDataPrimitiveValue.Type { let config = primitiveType.coreDataValue - self.attributeType = config.attributeType - self.isOptional = config.isOptional + self.attributeType = config.attributeType + self.isOptional = config.isOptional if let newClassName = config.attributeValueClassName { self.attributeValueClassName = newClassName } @@ -55,8 +55,8 @@ extension CoreData.NSAttributeDescription: SchemaProperty { let rawType = type.RawValue.self if let primitiveType = rawType as? CoreDataPrimitiveValue.Type { let config = primitiveType.coreDataValue - self.attributeType = config.attributeType - self.isOptional = config.isOptional + self.attributeType = config.attributeType + self.isOptional = config.isOptional if let newClassName = config.attributeValueClassName { self.attributeValueClassName = newClassName } @@ -75,12 +75,14 @@ extension CoreData.NSAttributeDescription: SchemaProperty { self.isOptional = newValue is any AnyOptional.Type func setValueClassName(for type: T.Type) { - self.attributeValueClassName = NSStringFromClass(CodableBox.self) + #if false // doesn't work + self.attributeValueClassName = NSStringFromClass(T.self) + #endif - let name = NSStringFromClass(CodableBox.Transformer.self) + let name = NSStringFromClass(CodableTransformer.self) if !ValueTransformer.valueTransformerNames().contains(.init(name)) { // no access to valueTransformerForName? - let transformer = CodableBox.Transformer() + let transformer = CodableTransformer() ValueTransformer .setValueTransformer(transformer, forName: .init(name)) } diff --git a/Tests/ManagedModelTests/CodablePropertiesTest.swift b/Tests/ManagedModelTests/CodablePropertiesTest.swift index 7639fef..4bd1213 100644 --- a/Tests/ManagedModelTests/CodablePropertiesTest.swift +++ b/Tests/ManagedModelTests/CodablePropertiesTest.swift @@ -32,15 +32,15 @@ final class CodablePropertiesTests: XCTestCase { let transformerName = try XCTUnwrap( ValueTransformer.valueTransformerNames().first(where: { - $0.rawValue.range(of: "CodableBox11TransformerVOO17ManagedModelTests8") + $0.rawValue.range(of: "CodableTransformerVOO17ManagedModelTests8") != nil }) ) let transformer = try XCTUnwrap(ValueTransformer(forName: transformerName)) _ = transformer // to clear unused-wraning - XCTAssertTrue(attribute.valueType == - CodableBox.self) + XCTAssertTrue(attribute.valueType == Any.self) + // Fixtures.CodablePropertiesSchema.AccessSIP.self XCTAssertNotNil(attribute.valueTransformerName) XCTAssertEqual(attribute.valueTransformerName, transformerName.rawValue) } @@ -54,7 +54,7 @@ final class CodablePropertiesTests: XCTestCase { // CodableBox. let transformerName = try XCTUnwrap( ValueTransformer.valueTransformerNames().first(where: { - $0.rawValue.range(of: "CodableBox11TransformerVOO17ManagedModelTests8") + $0.rawValue.range(of: "CodableTransformerVOO17ManagedModelTests8") != nil }) ) @@ -63,8 +63,8 @@ final class CodablePropertiesTests: XCTestCase { let attribute = try XCTUnwrap(entity.attributesByName["sip"]) XCTAssertEqual(attribute.name, "sip") - XCTAssertTrue(attribute.valueType == - CodableBox.self) + XCTAssertTrue(attribute.valueType == Any.self) + // Fixtures.CodablePropertiesSchema.AccessSIP.self) XCTAssertNotNil(attribute.valueTransformerName) XCTAssertEqual(attribute.valueTransformerName, transformerName.rawValue) }