90 lines
3.1 KiB
Swift
90 lines
3.1 KiB
Swift
//
|
|
// 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<Enclosing: NSManagedObject, Value: Codable> {
|
|
|
|
private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?>
|
|
private let fallback: Value
|
|
private var value: Value?
|
|
private var observation: NSKeyValueObservation?
|
|
private var skipClearingOnNextUpdate = false
|
|
|
|
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.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<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.
|
|
struct Box: Codable {
|
|
let value: Value
|
|
}
|
|
}
|