Fix timeline position sync not working due to LazilyDecoding cache not being invalidated upon remote change

This commit is contained in:
Shadowfacts 2023-01-28 13:41:22 -05:00
parent aec5c0b787
commit 7b7c05ff68
4 changed files with 24 additions and 7 deletions

View File

@ -545,6 +545,8 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
guard let timelinePosition = try? self.viewContext.existingObject(with: id) as? TimelinePosition else {
continue
}
// the kvo observer that clears the LazilyDecoding cache doesn't always fire on remote changes, so do it manually
timelinePosition.changedRemotely()
NotificationCenter.default.post(name: .timelinePositionChanged, object: timelinePosition)
}
if changedAccountPrefs {

View File

@ -41,6 +41,10 @@ public final class TimelinePosition: NSManagedObject {
self.createdAt = Date()
}
func changedRemotely() {
_statusIDs.removeCachedValue()
}
}
// blergh, this is the simplest way of getting the Timeline into a format that A) CoreData can handle and B) is usable in the predicate

View File

@ -18,6 +18,7 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
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
@ -37,13 +38,16 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
} else {
guard let data = instance[keyPath: wrapper.keyPath] else { return wrapper.fallback }
do {
let value = try decoder.decode(Box<Value>.self, from: data)
let value = try decoder.decode(Box.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
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
@ -55,12 +59,18 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
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 {
@ -72,7 +82,7 @@ extension LazilyDecoding {
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
struct Box: Codable {
let value: Value
}
}

View File

@ -589,6 +589,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
guard timelinePosition.centerStatusID != centerVisibleStatusID else {
return false
}
stateRestorationLogger.info("Potential restore with centerStatusID: \(timelinePosition.centerStatusID ?? "<none>")")
if !alwaysPrompt {
Task {
_ = await restoreState()