Skip to content

Instantly share code, notes, and snippets.

@Bonney
Created November 29, 2023 14:12
Show Gist options
  • Select an option

  • Save Bonney/0c4666989e74b5eb875b0ce245c00827 to your computer and use it in GitHub Desktop.

Select an option

Save Bonney/0c4666989e74b5eb875b0ce245c00827 to your computer and use it in GitHub Desktop.
This appears the be the bare-minimum workaround currently needed to use Swift's `Measurement` type in SwiftData.
@Model
final public class Drink {
/// Holds the JSON-encoded data blob representing the Measurement.
private var _measurement: Data?
/// The actual Measurement, encoded/decoded on-the-fly.
@Transient var measurement: Measurement<UnitVolume> {
get { getMeasurement() }
set { setMeasurement(newValue) }
}
private func getMeasurement() -> Measurement<UnitVolume> {
_$observationRegistrar.access(self, keyPath: \._measurement)
if let data = self.getValue(forKey: \._measurement) {
do {
return try JSONDecoder().decode(Measurement<UnitVolume>.self, from: data)
} catch {
print(error)
}
}
return Measurement(value: 0.0, unit: UnitVolume.fluidOunces)
}
private func setMeasurement(_ newValue: Measurement<UnitVolume>) {
_$observationRegistrar.withMutation(of: self, keyPath: \._measurement) {
do {
let data = try JSONEncoder().encode(newValue)
self.setValue(forKey: \._measurement, to: data)
} catch {
print(error)
self.setValue(forKey: \._measurement, to: nil)
}
}
}
}
@Bonney
Copy link
Author

Bonney commented Feb 10, 2024

This seems like a good candidate for a Macro, and not even one that's restricted to this use case.

Now to learn how to write macros...

Basically a macro should be able to transform this:

@JSONEncoded public var myValue: T          = T() // Provide a default value for non-nil values.
@JSONEncoded public var myOptionalValue: T? = nil // or have the property always optional

in to this:

private var myValueData: Data?

@Transient public var myValue: T {
    get { get_myValue() }
    set { set_myValue(newValue) }
}

 private func get_myValue() -> T {
    _$observationRegistrar.access(self, keyPath: \.myValueData)
    if let data = self.getValue(forKey: \.myValueData) {
        do {
            return try JSONDecoder().decode(T.self, from: data)
        } catch {
            print(error)
        }
    }
    return T // or nil for optional properties, whichever is provided as the default value
}

private func set_myValue(_ newValue: T) {
    _$observationRegistrar.withMutation(of: self, keyPath: \.myValueData) {
        do {
            let data = try JSONEncoder().encode(newValue)
            self.setValue(forKey: \.myValueData, to: data)
        } catch {
            print(error)
            self.setValue(forKey: \.myValueData, to: nil)
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment