How to store Measurement in NSUbiquitousKeyValueStore?

45 Views Asked by At

I have a Measurement<UnitMass> object that I want to store in NSUbiquitousKeyValueStore. I use Measurement because I need to convert values between various locale on a regular basis.

I have tried extending Measurement. I imagine there are more than one problem with the code below but the place I got stuck at was how to deal with UnitMass or UnitType conversion. (I would prefer if I could use UnitType in the code, so I could reuse it for other measurements.

extension Measurement {
    init(data: NSData) {
        let coding = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Coding.self, from: data as Data)
        self.value = coding?.value as! Double
        self.unit = coding.unit as UnitMass
    }

    func asData() -> NSData {
        return try! NSKeyedArchiver.archivedData(withRootObject: Coding(self), requiringSecureCoding: true) as NSData
    }

    class Coding: NSObject, NSCoding {
        func encode(with coder: NSCoder) {
            coder.encode(value, forKey: "value")
            coder.encode(unit, forKey: "unit")
        }

        let value: NSDecimalNumber
        let unit: NSObject

        init(_ measurement: Measurement) {
            value = NSDecimalNumber(value: measurement.value)
            unit = measurement.unit
        }

        required init?(coder aDecoder: NSCoder) {
            self.value = aDecoder.decodeObject(forKey: "value") as! NSDecimalNumber
            self.unit = aDecoder.decodeObject(forKey: "unit") as! NSObject
        }
    }
}

Of course, my first attempt was to simply store.set(weightAsMeasurement, forKey: "weight") and then store.object(forKey: "Weight") as? Measurement<UnitMass> but, of course, the compiler has no idea how to decode that.

I've also considered storing the value and unit separately. Again, I got stuck at the unit.

Any advice welcome.


Edit:

For the time being, I decided to store only the value:

  1. Convert the measurement to an explicit unit using .converted(to: .kilogram) (I used kilograms).
  2. Store only the value using convertedMeasurement.value.

When reading from the store:

  1. Use ‘store.double(forKey: “weight”)` to read the value from the store.
  2. Initialize the measurement for the explicitly selected unit using Measurement(value: value, unit: UnitMass.kilogram) (or whatever unit was used when saving to the store).
  3. Convert the measurement to the current locale using measurementInKilogram.converted(to: Locale.current)

Is there a better way to do it?

1

There are 1 best solutions below

1
vadian On

Measurement conforms to Codable, just use JSONEn-/Decoder

extension Measurement {
    init?(data: Data) {
        do {
            self = try JSONDecoder().decode(Measurement.self, from: data)
        } catch {
            return nil
        }
    }

    func asData() -> Data {
        return try! JSONEncoder().encode(self)
    }
}