From 21e9ca990d99620e15fceb996f91c6b43db681a3 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 11 May 2022 19:10:38 -0400 Subject: [PATCH] Use async/await for conversation loading --- .../MastodonCachePersistentStore.swift | 18 ++++ .../ConversationTableViewController.swift | 86 +++++++++---------- Tusker/Views/Toast/ToastConfiguration.swift | 8 ++ 3 files changed, 65 insertions(+), 47 deletions(-) diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index 95c7f231..cb934877 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -87,6 +87,16 @@ class MastodonCachePersistentStore: NSPersistentContainer { self.statusSubject.send(status.id) } } + + @MainActor + func addOrUpdateOnViewContext(status: Status) -> StatusMO { + let statusMO = self.upsert(status: status, context: viewContext) + if viewContext.hasChanges { + try! viewContext.save() + } + statusSubject.send(status.id) + return statusMO + } func addAll(statuses: [Status], completion: (() -> Void)? = nil) { backgroundContext.perform { @@ -98,6 +108,14 @@ class MastodonCachePersistentStore: NSPersistentContainer { completion?() } } + + func addAll(statuses: [Status]) async { + return await withCheckedContinuation { continuation in + addAll(statuses: statuses) { + continuation.resume() + } + } + } func account(for id: String, in context: NSManagedObjectContext? = nil) -> AccountMO? { let context = context ?? viewContext diff --git a/Tusker/Screens/Conversation/ConversationTableViewController.swift b/Tusker/Screens/Conversation/ConversationTableViewController.swift index f960c662..3c8f3e7c 100644 --- a/Tusker/Screens/Conversation/ConversationTableViewController.swift +++ b/Tusker/Screens/Conversation/ConversationTableViewController.swift @@ -131,41 +131,38 @@ class ConversationTableViewController: EnhancedTableViewController { } navigationItem.rightBarButtonItem = visibilityBarButtonItem - loadMainStatus() + Task { + await loadMainStatus() + } } - private func loadMainStatus() { + @MainActor + private func loadMainStatus() async { guard loadingState == .unloaded else { return } if let mainStatus = mastodonController.persistentContainer.status(for: mainStatusID) { - self.mainStatusLoaded(mainStatus) + await mainStatusLoaded(mainStatus) } else { loadingState = .loadingMain - let request = Client.getStatus(id: mainStatusID) - mastodonController.run(request) { (response) in - switch response { - case let .success(status, _): - let viewContext = self.mastodonController.persistentContainer.viewContext - self.mastodonController.persistentContainer.addOrUpdate(status: status, context: viewContext) { (statusMO) in - self.mainStatusLoaded(statusMO) - } - - case let .failure(error): - DispatchQueue.main.async { - self.loadingState = .unloaded - - let config = ToastConfiguration(from: error, with: "Error Loading Status", in: self) { [weak self] (toast) in - toast.dismissToast(animated: true) - self?.loadMainStatus() - } - self.showToast(configuration: config, animated: true) - } + let req = Client.getStatus(id: mainStatusID) + do { + let (status, _) = try await mastodonController.run(req) + let statusMO = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status) + await mainStatusLoaded(statusMO) + } catch { + let error = error as! Client.Error + loadingState = .unloaded + let config = ToastConfiguration(from: error, with: "Error Loading Status", in: self) { [weak self] toast in + toast.dismissToast(animated: true) + await self?.loadMainStatus() } + showToast(configuration: config, animated: true) + return } } } - private func mainStatusLoaded(_ mainStatus: StatusMO) { + private func mainStatusLoaded(_ mainStatus: StatusMO) async { let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState) var snapshot = NSDiffableDataSourceSnapshot() @@ -175,10 +172,11 @@ class ConversationTableViewController: EnhancedTableViewController { loadingState = .loadedMain - loadContext(for: mainStatus) + await loadContext(for: mainStatus) } - private func loadContext(for mainStatus: StatusMO) { + @MainActor + private func loadContext(for mainStatus: StatusMO) async { guard loadingState == .loadedMain else { return } loadingState = .loadingContext @@ -188,30 +186,24 @@ class ConversationTableViewController: EnhancedTableViewController { // todo: it would be nice to cache these contexts let request = Status.getContext(mainStatusID) - mastodonController.run(request) { response in - switch response { - case let .success(context, _): - let parentIDs = self.getDirectParents(inReplyTo: mainStatusInReplyToID, from: context.ancestors) - let parentStatuses = context.ancestors.filter { parentIDs.contains($0.id) } + do { + let (context, _) = try await mastodonController.run(request) + let parentIDs = self.getDirectParents(inReplyTo: mainStatusInReplyToID, from: context.ancestors) + let parentStatuses = context.ancestors.filter { parentIDs.contains($0.id) } + + // todo: should this really be blindly adding all the descendants? + await mastodonController.persistentContainer.addAll(statuses: parentStatuses + context.descendants) + self.contextLoaded(mainStatus: mainStatus, context: context, parentIDs: parentIDs) + + } catch { + let error = error as! Client.Error + self.loadingState = .loadedMain - // todo: should this really be blindly adding all the descendants? - self.mastodonController.persistentContainer.addAll(statuses: parentStatuses + context.descendants) { - DispatchQueue.main.async { - self.contextLoaded(mainStatus: mainStatus, context: context, parentIDs: parentIDs) - } - } - - case let .failure(error): - DispatchQueue.main.async { - self.loadingState = .loadedMain - - let config = ToastConfiguration(from: error, with: "Error Loading Content", in: self) { [weak self] (toast) in - toast.dismissToast(animated: true) - self?.loadContext(for: mainStatus) - } - self.showToast(configuration: config, animated: true) - } + let config = ToastConfiguration(from: error, with: "Error Loading Content", in: self) { [weak self] (toast) in + toast.dismissToast(animated: true) + await self?.loadContext(for: mainStatus) } + self.showToast(configuration: config, animated: true) } } diff --git a/Tusker/Views/Toast/ToastConfiguration.swift b/Tusker/Views/Toast/ToastConfiguration.swift index 464973dd..3a187946 100644 --- a/Tusker/Views/Toast/ToastConfiguration.swift +++ b/Tusker/Views/Toast/ToastConfiguration.swift @@ -57,6 +57,14 @@ extension ToastConfiguration { viewController.present(reporter, animated: true) } } + + init(from error: Client.Error, with title: String, in viewController: UIViewController, retryAction: @escaping @MainActor (ToastView) async -> Void) { + self.init(from: error, with: title, in: viewController) { toast in + Task { + await retryAction(toast) + } + } + } } fileprivate extension Client.Error {