From bff7585fa9a7cdfea5b7cc01b372b97e4f59f6ac Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Thu, 11 May 2023 23:03:41 -0400 Subject: [PATCH] Move remote change processing to separate context to avoid blocking background context --- .../MastodonCachePersistentStore.swift | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index 1859cdb2..9ea45356 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -31,18 +31,21 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer { context.persistentStoreCoordinator = self.persistentStoreCoordinator context.automaticallyMergesChangesFromParent = true context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump + context.name = "Background" return context }() - private(set) lazy var prefetchBackgroundContext: NSManagedObjectContext = { + // remote change processing happens on its own context, since it can sometimes take + // a really long time (upwards of a minute) and shouldn't block other things using the background context + private lazy var remoteChangesBackgroundContext: NSManagedObjectContext = { let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) context.persistentStoreCoordinator = self.persistentStoreCoordinator context.automaticallyMergesChangesFromParent = true context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump + context.name = "RemoteChanges" return context }() - private var remoteChangeHandlerQueue = DispatchQueue(label: "PersistentStore remote changes") private var lastRemoteChangeToken: NSPersistentHistoryToken? // TODO: consider sending managed objects through this to avoid re-fetching things unnecessarily @@ -182,6 +185,7 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer { viewContext.automaticallyMergesChangesFromParent = true viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump + viewContext.name = "View" NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: viewContext) NotificationCenter.default.addObserver(self, selector: #selector(remoteChanges), name: .NSPersistentStoreRemoteChange, object: persistentStoreCoordinator) @@ -509,50 +513,48 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer { guard let token = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken else { return } - remoteChangeHandlerQueue.async { + remoteChangesBackgroundContext.perform { defer { self.lastRemoteChangeToken = token } let req = NSPersistentHistoryChangeRequest.fetchHistory(after: self.lastRemoteChangeToken) - self.backgroundContext.performAndWait { - if let result = try? self.backgroundContext.execute(req) as? NSPersistentHistoryResult, - let transactions = result.result as? [NSPersistentHistoryTransaction], - !transactions.isEmpty { - var changedHashtags = false - var changedInstances = false - var changedTimelinePositions = Set() - var changedAccountPrefs = false - outer: for transaction in transactions { - for change in transaction.changes ?? [] { - if change.changedObjectID.entity.name == "SavedHashtag" { - changedHashtags = true - } else if change.changedObjectID.entity.name == "SavedInstance" { - changedInstances = true - } else if change.changedObjectID.entity.name == "TimelinePosition" { - changedTimelinePositions.insert(change.changedObjectID) - } else if change.changedObjectID.entity.name == "AccountPreferences" { - changedAccountPrefs = true - } + if let result = try? self.remoteChangesBackgroundContext.execute(req) as? NSPersistentHistoryResult, + let transactions = result.result as? [NSPersistentHistoryTransaction], + !transactions.isEmpty { + var changedHashtags = false + var changedInstances = false + var changedTimelinePositions = Set() + var changedAccountPrefs = false + outer: for transaction in transactions { + for change in transaction.changes ?? [] { + if change.changedObjectID.entity.name == "SavedHashtag" { + changedHashtags = true + } else if change.changedObjectID.entity.name == "SavedInstance" { + changedInstances = true + } else if change.changedObjectID.entity.name == "TimelinePosition" { + changedTimelinePositions.insert(change.changedObjectID) + } else if change.changedObjectID.entity.name == "AccountPreferences" { + changedAccountPrefs = true } } - DispatchQueue.main.async { - if changedHashtags { - NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil) - } - if changedInstances { - NotificationCenter.default.post(name: .savedInstancesChanged, object: nil) - } - for id in changedTimelinePositions { - 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 { - NotificationCenter.default.post(name: .accountPreferencesChangedRemotely, object: nil) + } + DispatchQueue.main.async { + if changedHashtags { + NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil) + } + if changedInstances { + NotificationCenter.default.post(name: .savedInstancesChanged, object: nil) + } + for id in changedTimelinePositions { + 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 { + NotificationCenter.default.post(name: .accountPreferencesChangedRemotely, object: nil) } } }