diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift
index 81dc89e1..33b4acfc 100644
--- a/Tusker/CoreData/MastodonCachePersistentStore.swift
+++ b/Tusker/CoreData/MastodonCachePersistentStore.swift
@@ -37,17 +37,23 @@ class MastodonCachePersistentStore: NSPersistentContainer {
}
}
- private func upsert(status: Status) {
+ private func upsert(status: Status, incrementReferenceCount: Bool) {
if let statusMO = self.status(for: status.id, in: self.backgroundContext) {
statusMO.updateFrom(apiStatus: status, container: self)
+ if incrementReferenceCount {
+ statusMO.incrementReferenceCount()
+ }
} else {
- _ = StatusMO(apiStatus: status, container: self, context: self.backgroundContext)
+ let statusMO = StatusMO(apiStatus: status, container: self, context: self.backgroundContext)
+ if incrementReferenceCount {
+ statusMO.incrementReferenceCount()
+ }
}
}
- func addOrUpdate(status: Status, save: Bool = true) {
+ func addOrUpdate(status: Status, incrementReferenceCount: Bool, save: Bool = true) {
backgroundContext.perform {
- self.upsert(status: status)
+ self.upsert(status: status, incrementReferenceCount: incrementReferenceCount)
if save, self.backgroundContext.hasChanges {
try! self.backgroundContext.save()
}
@@ -56,7 +62,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
backgroundContext.perform {
- statuses.forEach(self.upsert(status:))
+ statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) }
if self.backgroundContext.hasChanges {
try! self.backgroundContext.save()
}
diff --git a/Tusker/CoreData/StatusMO.swift b/Tusker/CoreData/StatusMO.swift
index aed68312..f413ee78 100644
--- a/Tusker/CoreData/StatusMO.swift
+++ b/Tusker/CoreData/StatusMO.swift
@@ -35,6 +35,7 @@ public final class StatusMO: NSManagedObject {
@NSManaged public var pinned: Bool
@NSManaged public var reblogged: Bool
@NSManaged public var reblogsCount: Int
+ @NSManaged public var referenceCount: Int
@NSManaged public var sensitive: Bool
@NSManaged public var spoilerText: String
@NSManaged public var uri: String // todo: are both uri and url necessary?
@@ -64,12 +65,30 @@ public final class StatusMO: NSManagedObject {
}
}
+ func incrementReferenceCount() {
+ referenceCount += 1
+ }
+
+ func decrementReferenceCount() {
+ referenceCount -= 1
+ if referenceCount <= 0 {
+ managedObjectContext!.delete(self)
+ }
+ }
+
+ public override func prepareForDeletion() {
+ super.prepareForDeletion()
+ reblog?.decrementReferenceCount()
+ }
+
}
extension StatusMO {
convenience init(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore, context: NSManagedObjectContext) {
self.init(context: context)
self.updateFrom(apiStatus: status, container: container)
+
+ reblog?.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 67b5f931..c4e2508a 100644
--- a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents
+++ b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents
@@ -43,6 +43,7 @@
+
@@ -58,6 +59,6 @@
-
+
\ No newline at end of file
diff --git a/Tusker/MastodonCache.swift b/Tusker/MastodonCache.swift
index 2d50bee4..9aec17c6 100644
--- a/Tusker/MastodonCache.swift
+++ b/Tusker/MastodonCache.swift
@@ -59,12 +59,10 @@ class MastodonCache {
func add(status: Status) {
set(status: status, for: status.id)
- mastodonController?.persistentContainer.addOrUpdate(status: status)
}
func addAll(statuses: [Status]) {
statuses.forEach(add)
- mastodonController?.persistentContainer.addAll(statuses: statuses)
}
// MARK: - Accounts
@@ -94,12 +92,10 @@ class MastodonCache {
func add(account: Account) {
set(account: account, for: account.id)
- mastodonController?.persistentContainer.addOrUpdate(account: account)
}
func addAll(accounts: [Account]) {
accounts.forEach(add)
- mastodonController?.persistentContainer.addAll(accounts: accounts)
}
// MARK: - Relationships
diff --git a/Tusker/SceneDelegate.swift b/Tusker/SceneDelegate.swift
index 5755676f..10eba605 100644
--- a/Tusker/SceneDelegate.swift
+++ b/Tusker/SceneDelegate.swift
@@ -109,6 +109,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
+
+ try! scene.session.mastodonController?.persistentContainer.viewContext.save()
}
func activateAccount(_ account: LocalData.UserAccountInfo) {
diff --git a/Tusker/Screens/Timeline/TimelineTableViewController.swift b/Tusker/Screens/Timeline/TimelineTableViewController.swift
index 8d07441e..e10f83f8 100644
--- a/Tusker/Screens/Timeline/TimelineTableViewController.swift
+++ b/Tusker/Screens/Timeline/TimelineTableViewController.swift
@@ -19,6 +19,9 @@ class TimelineTableViewController: EnhancedTableViewController {
var newer: RequestRange?
var older: RequestRange?
+ private var prevScrollViewContentOffset: CGPoint?
+ private var scrollViewDirection: CGFloat = 0
+
init(for timeline: Timeline, mastodonController: MastodonController) {
self.timeline = timeline
self.mastodonController = mastodonController
@@ -38,6 +41,17 @@ class TimelineTableViewController: EnhancedTableViewController {
fatalError("init(coder:) has not been implemented")
}
+ deinit {
+ // decrement reference counts of any statuses we still have
+ // if the app is currently being quit, this will not affect the persisted data because
+ // the persistent container would already have been saved in SceneDelegate.sceneDidEnterBackground(_:)
+ for segment in timelineSegments {
+ for (id, _) in segment {
+ mastodonController.persistentContainer.status(for: id)?.decrementReferenceCount()
+ }
+ }
+ }
+
func statusID(for indexPath: IndexPath) -> String {
return timelineSegments[indexPath.section][indexPath.row].id
}
@@ -59,7 +73,6 @@ class TimelineTableViewController: EnhancedTableViewController {
let request = Client.getStatuses(timeline: timeline)
mastodonController.run(request) { response in
guard case let .success(statuses, pagination) = response else { fatalError() }
-// self.mastodonController.cache.addAll(statuses: statuses)
// todo: possible race condition here? we update the underlying data before waiting to reload the table view
self.timelineSegments.insert(statuses.map { ($0.id, .unknown) }, at: 0)
self.newer = pagination?.newer
@@ -96,6 +109,59 @@ class TimelineTableViewController: EnhancedTableViewController {
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
+ // when scrolling upwards, decrement reference counts for old statuses, if necessary
+ if scrollViewDirection < 0 {
+ if indexPath.section <= timelineSegments.count - 2 {
+ // decrement ref counts for all sections below the section below the current section
+ // (e.g., there exist sections 0, 1, 2 and we're currently scrolling upwards in section 0, we want to remove section 2)
+
+ // todo: this is in the hot path for scrolling, possibly move this to a background thread?
+ let sectionsToRemove = indexPath.section + 1.. 2 * pageSize,
+ indexPath.row < lastSection.count - (2 * pageSize) {
+ // todo: this is in the hot path for scrolling, possibly move this to a background thread?
+ let statusesToRemove = lastSection[lastSection.count - pageSize..