diff --git a/Tusker/Controllers/MastodonController.swift b/Tusker/Controllers/MastodonController.swift
index c3dcaaf9..a75a0ecc 100644
--- a/Tusker/Controllers/MastodonController.swift
+++ b/Tusker/Controllers/MastodonController.swift
@@ -84,8 +84,16 @@ class MastodonController {
run(request) { response in
guard case let .success(account, _) = response else { fatalError() }
self.account = account
- self.persistentContainer.addOrUpdate(account: account)
- completion?(account)
+ self.persistentContainer.backgroundContext.perform {
+ if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) {
+ accountMO.updateFrom(apiAccount: account, container: self.persistentContainer)
+ } else {
+ // the first time the user's account is added to the store,
+ // increment its reference count so that it's never removed
+ self.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true)
+ }
+ completion?(account)
+ }
}
}
}
diff --git a/Tusker/CoreData/AccountMO.swift b/Tusker/CoreData/AccountMO.swift
index ff41f5cc..8349246f 100644
--- a/Tusker/CoreData/AccountMO.swift
+++ b/Tusker/CoreData/AccountMO.swift
@@ -32,6 +32,7 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
@NSManaged public var locked: Bool
@NSManaged public var movedCD: Bool
@NSManaged public var note: String
+ @NSManaged public var referenceCount: Int
@NSManaged public var statusesCount: Int
@NSManaged public var url: URL
@NSManaged public var username: String
@@ -46,12 +47,30 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
public var bot: Bool? { botCD }
public var moved: Bool? { movedCD }
+ func incrementReferenceCount() {
+ referenceCount += 1
+ }
+
+ func decrementReferenceCount() {
+ referenceCount -= 1
+ if referenceCount <= 0 {
+ managedObjectContext!.delete(self)
+ }
+ }
+
+ public override func prepareForDeletion() {
+ super.prepareForDeletion()
+ movedTo?.decrementReferenceCount()
+ }
+
}
extension AccountMO {
convenience init(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore, context: NSManagedObjectContext) {
self.init(context: context)
self.updateFrom(apiAccount: account, container: container)
+
+ movedTo?.incrementReferenceCount()
}
func updateFrom(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore) {
diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift
index 30651066..419c4955 100644
--- a/Tusker/CoreData/MastodonCachePersistentStore.swift
+++ b/Tusker/CoreData/MastodonCachePersistentStore.swift
@@ -105,18 +105,25 @@ class MastodonCachePersistentStore: NSPersistentContainer {
}
@discardableResult
- private func upsert(account: Account) -> AccountMO {
+ private func upsert(account: Account, incrementReferenceCount: Bool) -> AccountMO {
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
accountMO.updateFrom(apiAccount: account, container: self)
+ if incrementReferenceCount {
+ accountMO.incrementReferenceCount()
+ }
return accountMO
} else {
- return AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
+ let accountMO = AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
+ if incrementReferenceCount {
+ accountMO.incrementReferenceCount()
+ }
+ return accountMO
}
}
- func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
+ func addOrUpdate(account: Account, incrementReferenceCount: Bool, completion: ((AccountMO) -> Void)? = nil) {
backgroundContext.perform {
- let accountMO = self.upsert(account: account)
+ let accountMO = self.upsert(account: account, incrementReferenceCount: incrementReferenceCount)
if self.backgroundContext.hasChanges {
try! self.backgroundContext.save()
}
@@ -127,7 +134,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, incrementReferenceCount: true) }
if self.backgroundContext.hasChanges {
try! self.backgroundContext.save()
}
@@ -139,9 +146,11 @@ class MastodonCachePersistentStore: NSPersistentContainer {
func addAll(notifications: [Pachyderm.Notification], completion: (() -> Void)? = nil) {
backgroundContext.perform {
let statuses = notifications.compactMap { $0.status }
- let accounts = notifications.map { $0.account }
+ // filter out mentions, otherwise we would double increment the reference count of those accounts
+ // 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, incrementReferenceCount: true) }
- accounts.forEach { self.upsert(account: $0) }
+ accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
if self.backgroundContext.hasChanges {
try! self.backgroundContext.save()
}
@@ -157,7 +166,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
var updatedStatuses = [String]()
block(self.backgroundContext, { (accounts) in
- accounts.forEach { self.upsert(account: $0) }
+ accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
updatedAccounts.append(contentsOf: accounts.map { $0.id })
}, { (statuses) in
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) }
diff --git a/Tusker/CoreData/StatusMO.swift b/Tusker/CoreData/StatusMO.swift
index 82c0728e..20b963bc 100644
--- a/Tusker/CoreData/StatusMO.swift
+++ b/Tusker/CoreData/StatusMO.swift
@@ -82,6 +82,7 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
public override func prepareForDeletion() {
super.prepareForDeletion()
reblog?.decrementReferenceCount()
+ account.decrementReferenceCount()
}
}
@@ -92,6 +93,7 @@ extension StatusMO {
self.updateFrom(apiStatus: status, container: container)
reblog?.incrementReferenceCount()
+ account.incrementReferenceCount()
}
func updateFrom(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore) {
diff --git a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents
index 67a49980..2af9efec 100644
--- a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents
+++ b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents
@@ -15,6 +15,7 @@
+
@@ -49,7 +50,6 @@
-
@@ -59,7 +59,7 @@
-
+
\ No newline at end of file
diff --git a/Tusker/Screens/Profile/ProfileTableViewController.swift b/Tusker/Screens/Profile/ProfileTableViewController.swift
index dd81a404..b5e76b13 100644
--- a/Tusker/Screens/Profile/ProfileTableViewController.swift
+++ b/Tusker/Screens/Profile/ProfileTableViewController.swift
@@ -61,6 +61,15 @@ class ProfileTableViewController: EnhancedTableViewController {
fatalError("init(coder:) has not been implemeneted")
}
+ deinit {
+ if let id = accountID {
+ let container = mastodonController.persistentContainer
+ container.backgroundContext.perform {
+ container.account(for: id, in: container.backgroundContext)?.decrementReferenceCount()
+ }
+ }
+ }
+
override func viewDidLoad() {
super.viewDidLoad()
@@ -90,7 +99,7 @@ class ProfileTableViewController: EnhancedTableViewController {
}
return
}
- self.mastodonController.persistentContainer.addOrUpdate(account: account) { (_) in
+ self.mastodonController.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true) { (_) in
DispatchQueue.main.async {
self.updateAccountUI()
self.tableView.reloadData()
diff --git a/Tusker/Screens/Search/SearchResultsViewController.swift b/Tusker/Screens/Search/SearchResultsViewController.swift
index ba4b7fff..d91522f5 100644
--- a/Tusker/Screens/Search/SearchResultsViewController.swift
+++ b/Tusker/Screens/Search/SearchResultsViewController.swift
@@ -134,11 +134,10 @@ class SearchResultsViewController: EnhancedTableViewController {
var snapshot = NSDiffableDataSourceSnapshot()
self.mastodonController.persistentContainer.performBatchUpdates({ (context, addAccounts, addStatuses) in
- // todo: reference count accounts
-// oldSnapshot.itemIdentifiers(inSection: .accounts).forEach { (item) in
-// guard case let .account(id) = item else { return }
-// self.mastodonController.persistentContainer.account(for: id, in: context)?.decrementReferenceCount()
-// }
+ oldSnapshot.itemIdentifiers(inSection: .accounts).forEach { (item) in
+ guard case let .account(id) = item else { return }
+ self.mastodonController.persistentContainer.account(for: id, in: context)?.decrementReferenceCount()
+ }
oldSnapshot.itemIdentifiers(inSection: .statuses).forEach { (item) in
guard case let .status(id, _) = item else { return }
diff --git a/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift b/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift
index 7690352b..0145b887 100644
--- a/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift
+++ b/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift
@@ -58,6 +58,17 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
fatalError("init(coder:) has not been implemented")
}
+ deinit {
+ if let accountIDs = self.accountIDs {
+ let container = self.mastodonController.persistentContainer
+ container.backgroundContext.perform {
+ for id in accountIDs {
+ container.account(for: id, in: container.backgroundContext)?.decrementReferenceCount()
+ }
+ }
+ }
+ }
+
override func viewDidLoad() {
super.viewDidLoad()
@@ -71,7 +82,11 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
- if accountIDs == nil {
+ if let accountIDs = accountIDs {
+ accountIDs.forEach { (id) in
+ self.mastodonController.persistentContainer.account(for: id)?.incrementReferenceCount()
+ }
+ } else {
// account IDs haven't been set, so perform a request to load them
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
fatalError("Missing cached status \(statusID)")