// // LazyDecoding.swift // Tusker // // Created by Shadowfacts on 4/11/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import Foundation import CoreData private let decoder = PropertyListDecoder() private let encoder = PropertyListEncoder() @propertyWrapper public struct LazilyDecoding { private let keyPath: ReferenceWritableKeyPath private let fallback: Value private var value: Value? private var observation: NSKeyValueObservation? private var skipClearingOnNextUpdate = false init(from keyPath: ReferenceWritableKeyPath, 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, storage storageKeyPath: ReferenceWritableKeyPath) -> 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.self, from: data) wrapper.value = value.value wrapper.observation = instance.observe(wrapper.keyPath, changeHandler: { instance, _ in var wrapper = instance[keyPath: storageKeyPath] if wrapper.skipClearingOnNextUpdate { wrapper.skipClearingOnNextUpdate = false } else { wrapper.removeCachedValue() } instance[keyPath: storageKeyPath] = wrapper }) instance[keyPath: storageKeyPath] = wrapper return value.value } catch { return wrapper.fallback } } } set { var wrapper = instance[keyPath: storageKeyPath] wrapper.value = newValue wrapper.skipClearingOnNextUpdate = true instance[keyPath: storageKeyPath] = wrapper let newData = try! encoder.encode(Box(value: newValue)) instance[keyPath: wrapper.keyPath] = newData } } mutating func removeCachedValue() { value = nil observation = nil } } extension LazilyDecoding { init(arrayFrom keyPath: ReferenceWritableKeyPath) 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. struct Box: Codable { let value: Value } }