Tusker/Tusker/LazilyDecoding.swift

79 lines
2.8 KiB
Swift

//
// LazyDecoding.swift
// Tusker
//
// Created by Shadowfacts on 4/11/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Foundation
private let decoder = PropertyListDecoder()
private let encoder = PropertyListEncoder()
@propertyWrapper
public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?>
private let fallback: Value
private var value: Value?
private var observation: NSKeyValueObservation?
init(from keyPath: ReferenceWritableKeyPath<Enclosing, Data?>, fallback: Value) {
self.keyPath = keyPath
self.fallback = fallback
}
public var wrappedValue: Value {
get { fatalError("called LazilyDecoding wrappedValue getter") }
set { fatalError("called LazilyDecoding wrappedValue setter") }
}
public static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath<Enclosing, Value>, storage storageKeyPath: ReferenceWritableKeyPath<Enclosing, Self>) -> Value {
get {
var wrapper = instance[keyPath: storageKeyPath]
if let value = wrapper.value {
return value
} else {
guard let data = instance[keyPath: wrapper.keyPath] else { return wrapper.fallback }
do {
let value = try decoder.decode(Box<Value>.self, from: data)
wrapper.value = value.value
wrapper.observation = instance.observe(wrapper.keyPath, changeHandler: { instance, _ in
var updated = instance[keyPath: storageKeyPath]
updated.value = nil
updated.observation = nil
instance[keyPath: storageKeyPath] = updated
})
instance[keyPath: storageKeyPath] = wrapper
return value.value
} catch {
return wrapper.fallback
}
}
}
set {
var wrapper = instance[keyPath: storageKeyPath]
wrapper.value = newValue
instance[keyPath: storageKeyPath] = wrapper
let newData = try! encoder.encode(Box(value: newValue))
instance[keyPath: wrapper.keyPath] = newData
}
}
}
extension LazilyDecoding {
init<T>(arrayFrom keyPath: ReferenceWritableKeyPath<Enclosing, Data?>) where Value == [T] {
self.init(from: keyPath, fallback: [])
}
}
extension LazilyDecoding {
// PropertyListEncoder only allows top-level types to be dicts or arrays, which breaks encoding nil-able values.
// Wrapping everything in a Box ensures that it's always a dict.
private struct Box<T: Codable>: Codable {
let value: T
}
}