From d5433e9b918b570a448cb2dedeabfcb17905cc21 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 29 Oct 2022 18:55:13 -0400 Subject: [PATCH] Fix crash when opening profile view controller with uncached account E.g., by tapping a mention in a status --- .../MastodonCachePersistentStore.swift | 21 +++++----- .../ProfileStatusesViewController.swift | 41 +++++++++++++++---- .../Profile/ProfileViewController.swift | 2 +- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index 4491d59f..fe867c48 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -147,19 +147,20 @@ class MastodonCachePersistentStore: NSPersistentContainer { } @discardableResult - private func upsert(account: Account) -> AccountMO { - if let accountMO = self.account(for: account.id, in: self.backgroundContext) { + private func upsert(account: Account, in context: NSManagedObjectContext) -> AccountMO { + if let accountMO = self.account(for: account.id, in: context) { accountMO.updateFrom(apiAccount: account, container: self) return accountMO } else { - return AccountMO(apiAccount: account, container: self, context: self.backgroundContext) + return AccountMO(apiAccount: account, container: self, context: context) } } - func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) { - backgroundContext.perform { - let accountMO = self.upsert(account: account) - self.save(context: self.backgroundContext) + func addOrUpdate(account: Account, in context: NSManagedObjectContext? = nil, completion: ((AccountMO) -> Void)? = nil) { + let context = context ?? backgroundContext + context.perform { + let accountMO = self.upsert(account: account, in: context) + self.save(context: context) completion?(accountMO) self.accountSubject.send(account.id) } @@ -199,7 +200,7 @@ class MastodonCachePersistentStore: NSPersistentContainer { func addAll(accounts: [Account], completion: (() -> Void)? = nil) { backgroundContext.perform { - accounts.forEach { self.upsert(account: $0) } + accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) } self.save(context: self.backgroundContext) completion?() accounts.forEach { self.accountSubject.send($0.id) } @@ -213,7 +214,7 @@ class MastodonCachePersistentStore: NSPersistentContainer { // since the status has the same account as the notification let accounts = notifications.filter { $0.kind != .mention }.map { $0.account } statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) } - accounts.forEach { self.upsert(account: $0) } + accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) } self.save(context: self.backgroundContext) completion?() statuses.forEach { self.statusSubject.send($0.id) } @@ -227,7 +228,7 @@ class MastodonCachePersistentStore: NSPersistentContainer { var updatedStatuses = [String]() block(self.backgroundContext, { (accounts) in - accounts.forEach { self.upsert(account: $0) } + accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) } updatedAccounts.append(contentsOf: accounts.map { $0.id }) }, { (statuses) in statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) } diff --git a/Tusker/Screens/Profile/ProfileStatusesViewController.swift b/Tusker/Screens/Profile/ProfileStatusesViewController.swift index d70e4599..99a38341 100644 --- a/Tusker/Screens/Profile/ProfileStatusesViewController.swift +++ b/Tusker/Screens/Profile/ProfileStatusesViewController.swift @@ -29,9 +29,10 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie view as! UICollectionView } private(set) var dataSource: UICollectionViewDiffableDataSource! - private(set) var headerCell: ProfileHeaderCollectionViewCell? + private var state: State = .unloaded + init(accountID: String?, kind: Kind, owner: ProfileViewController) { self.accountID = accountID self.kind = kind @@ -92,9 +93,18 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie .receive(on: DispatchQueue.main) .filter { [unowned self] in $0 == self.accountID } .sink { [unowned self] id in - var snapshot = dataSource.snapshot() - snapshot.reconfigureItems([.header(id)]) - dataSource.apply(snapshot, animatingDifferences: true) + switch state { + case .unloaded: + Task { + await load() + } + case .loading: + break + case .loaded, .addedHeader: + var snapshot = dataSource.snapshot() + snapshot.reconfigureItems([.header(id)]) + dataSource.apply(snapshot, animatingDifferences: true) + } } .store(in: &cancellables) } @@ -159,20 +169,26 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie } private func load() async { - guard accountID != nil, - await controller.state == .notLoadedInitial, - isViewLoaded else { + guard isViewLoaded, + let accountID, + case .unloaded = state, + mastodonController.persistentContainer.account(for: accountID) != nil else { return } + state = .loading + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.header, .pinned, .statuses]) snapshot.appendItems([.header(accountID)], toSection: .header) await apply(snapshot, animatingDifferences: false) - print("added header item") + + state = .addedHeader await controller.loadInitial() await tryLoadPinned() + + state = .loaded } private func tryLoadPinned() async { @@ -222,6 +238,15 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie } +extension ProfileStatusesViewController { + enum State { + case unloaded + case loading + case addedHeader + case loaded + } +} + extension ProfileStatusesViewController { enum Kind { case statuses, withReplies, onlyMedia diff --git a/Tusker/Screens/Profile/ProfileViewController.swift b/Tusker/Screens/Profile/ProfileViewController.swift index 47d3d794..2594ad30 100644 --- a/Tusker/Screens/Profile/ProfileViewController.swift +++ b/Tusker/Screens/Profile/ProfileViewController.swift @@ -116,7 +116,7 @@ class ProfileViewController: UIPageViewController { let req = Client.getAccount(id: accountID) let (account, _) = try await mastodonController.run(req) let mo = await withCheckedContinuation { continuation in - mastodonController.persistentContainer.addOrUpdate(account: account) { (mo) in + mastodonController.persistentContainer.addOrUpdate(account: account, in: mastodonController.persistentContainer.viewContext) { (mo) in continuation.resume(returning: mo) } }