From b40d8152740918cc36e219431086a8c0d345149f Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Fri, 10 Nov 2023 14:16:16 -0500 Subject: [PATCH] Ensure LazilyDecoding runs on the managed object context's thread Maybe fix the crash in KeyPath machinery? --- Tusker/LazilyDecoding.swift | 69 ++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/Tusker/LazilyDecoding.swift b/Tusker/LazilyDecoding.swift index 7d402eab..26fec37a 100644 --- a/Tusker/LazilyDecoding.swift +++ b/Tusker/LazilyDecoding.swift @@ -7,12 +7,13 @@ // import Foundation +import CoreData private let decoder = PropertyListDecoder() private let encoder = PropertyListEncoder() @propertyWrapper -public struct LazilyDecoding { +public struct LazilyDecoding { private let keyPath: ReferenceWritableKeyPath private let fallback: Value @@ -32,37 +33,41 @@ public struct LazilyDecoding { 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.performOnContext { + 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 - }) - instance[keyPath: storageKeyPath] = wrapper - return value.value - } catch { - return wrapper.fallback + 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 + instance.performOnContext { + 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 + } } } @@ -73,6 +78,16 @@ public struct LazilyDecoding { } +extension NSManagedObject { + fileprivate func performOnContext(_ f: () -> V) -> V { + if let managedObjectContext { + managedObjectContext.performAndWait(f) + } else { + f() + } + } +} + extension LazilyDecoding { init(arrayFrom keyPath: ReferenceWritableKeyPath) where Value == [T] { self.init(from: keyPath, fallback: [])