Ensure LazilyDecoding runs on the managed object context's thread

Maybe fix the crash in KeyPath machinery?
This commit is contained in:
Shadowfacts 2023-11-10 14:16:16 -05:00
parent bc7500bde9
commit b40d815274
1 changed files with 42 additions and 27 deletions

View File

@ -7,12 +7,13 @@
// //
import Foundation import Foundation
import CoreData
private let decoder = PropertyListDecoder() private let decoder = PropertyListDecoder()
private let encoder = PropertyListEncoder() private let encoder = PropertyListEncoder()
@propertyWrapper @propertyWrapper
public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> { public struct LazilyDecoding<Enclosing: NSManagedObject, Value: Codable> {
private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?> private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?>
private let fallback: Value private let fallback: Value
@ -32,37 +33,41 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
public static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath<Enclosing, Value>, storage storageKeyPath: ReferenceWritableKeyPath<Enclosing, Self>) -> Value { public static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath<Enclosing, Value>, storage storageKeyPath: ReferenceWritableKeyPath<Enclosing, Self>) -> Value {
get { get {
var wrapper = instance[keyPath: storageKeyPath] instance.performOnContext {
if let value = wrapper.value { var wrapper = instance[keyPath: storageKeyPath]
return value if let value = wrapper.value {
} else { return value
guard let data = instance[keyPath: wrapper.keyPath] else { return wrapper.fallback } } else {
do { guard let data = instance[keyPath: wrapper.keyPath] else { return wrapper.fallback }
let value = try decoder.decode(Box.self, from: data) do {
wrapper.value = value.value let value = try decoder.decode(Box.self, from: data)
wrapper.observation = instance.observe(wrapper.keyPath, changeHandler: { instance, _ in wrapper.value = value.value
var wrapper = instance[keyPath: storageKeyPath] wrapper.observation = instance.observe(wrapper.keyPath, changeHandler: { instance, _ in
if wrapper.skipClearingOnNextUpdate { var wrapper = instance[keyPath: storageKeyPath]
wrapper.skipClearingOnNextUpdate = false if wrapper.skipClearingOnNextUpdate {
} else { wrapper.skipClearingOnNextUpdate = false
wrapper.removeCachedValue() } else {
} wrapper.removeCachedValue()
}
instance[keyPath: storageKeyPath] = wrapper
})
instance[keyPath: storageKeyPath] = wrapper instance[keyPath: storageKeyPath] = wrapper
}) return value.value
instance[keyPath: storageKeyPath] = wrapper } catch {
return value.value return wrapper.fallback
} catch { }
return wrapper.fallback
} }
} }
} }
set { set {
var wrapper = instance[keyPath: storageKeyPath] instance.performOnContext {
wrapper.value = newValue var wrapper = instance[keyPath: storageKeyPath]
wrapper.skipClearingOnNextUpdate = true wrapper.value = newValue
instance[keyPath: storageKeyPath] = wrapper wrapper.skipClearingOnNextUpdate = true
let newData = try! encoder.encode(Box(value: newValue)) instance[keyPath: storageKeyPath] = wrapper
instance[keyPath: wrapper.keyPath] = newData let newData = try! encoder.encode(Box(value: newValue))
instance[keyPath: wrapper.keyPath] = newData
}
} }
} }
@ -73,6 +78,16 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
} }
extension NSManagedObject {
fileprivate func performOnContext<V>(_ f: () -> V) -> V {
if let managedObjectContext {
managedObjectContext.performAndWait(f)
} else {
f()
}
}
}
extension LazilyDecoding { extension LazilyDecoding {
init<T>(arrayFrom keyPath: ReferenceWritableKeyPath<Enclosing, Data?>) where Value == [T] { init<T>(arrayFrom keyPath: ReferenceWritableKeyPath<Enclosing, Data?>) where Value == [T] {
self.init(from: keyPath, fallback: []) self.init(from: keyPath, fallback: [])