From 3b87916d9a26610dc010c4dff7483c138606cc93 Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Tue, 7 Feb 2023 00:46:35 +0100 Subject: [PATCH] chore(Feed): Improve Feed performance by fetching statuses in batches (#914) --- .../HomeTimelineViewController.swift | 6 ++++++ .../HomeTimelineViewModel+LoadLatestState.swift | 2 +- .../HomeTimelineViewModel+LoadOldestState.swift | 2 +- .../HomeTimeline/HomeTimelineViewModel.swift | 6 ++++++ .../FeedFetchedResultsController.swift | 16 +++++++++++++++- 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 3caafef3d..6c390af3b 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -518,6 +518,12 @@ extension HomeTimelineViewController: UITableViewDelegate, AutoGenerateTableView } // sourcery:end + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 { + viewModel.timelineDidReachEnd() + } + } } // MARK: - TimelineMiddleLoaderTableViewCellDelegate diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift index d243c9a96..fdb9da01e 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift @@ -65,7 +65,7 @@ extension HomeTimelineViewModel.LoadLatestState { guard let viewModel else { return } let latestFeedRecords = viewModel.fetchedResultsController.records.prefix(APIService.onceRequestStatusMaxCount) - let parentManagedObjectContext = viewModel.fetchedResultsController.fetchedResultsController.managedObjectContext + let parentManagedObjectContext = viewModel.fetchedResultsController.managedObjectContext let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) managedObjectContext.parent = parentManagedObjectContext diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift index e88b5ed5f..2b02f0360 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift @@ -67,7 +67,7 @@ extension HomeTimelineViewModel.LoadOldestState { } Task { - let managedObjectContext = viewModel.fetchedResultsController.fetchedResultsController.managedObjectContext + let managedObjectContext = viewModel.fetchedResultsController.managedObjectContext let _maxID: Mastodon.Entity.Status.ID? = try await managedObjectContext.perform { guard let feed = lastFeedRecord.object(in: managedObjectContext), let status = feed.status diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index c7c967305..a59ffe7c5 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -119,6 +119,12 @@ extension HomeTimelineViewModel { } } +extension HomeTimelineViewModel { + func timelineDidReachEnd() { + fetchedResultsController.fetchNextBatch() + } +} + extension HomeTimelineViewModel { // load timeline gap diff --git a/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FeedFetchedResultsController.swift b/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FeedFetchedResultsController.swift index ab555c1c3..27cc5a08d 100644 --- a/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FeedFetchedResultsController.swift +++ b/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FeedFetchedResultsController.swift @@ -16,11 +16,19 @@ import MastodonSDK final public class FeedFetchedResultsController: NSObject { + private enum Constants { + static let defaultFetchLimit = 100 + } + public let logger = Logger(subsystem: "FeedFetchedResultsController", category: "DB") var disposeBag = Set() - public let fetchedResultsController: NSFetchedResultsController + private let fetchedResultsController: NSFetchedResultsController + + public var managedObjectContext: NSManagedObjectContext { + fetchedResultsController.managedObjectContext + } // input @Published public var predicate = Feed.predicate(kind: .none, acct: .none) @@ -28,6 +36,11 @@ final public class FeedFetchedResultsController: NSObject { // output private let _objectIDs = PassthroughSubject<[NSManagedObjectID], Never>() @Published public var records: [ManagedObjectRecord] = [] + + public func fetchNextBatch() { + fetchedResultsController.fetchRequest.fetchLimit += Constants.defaultFetchLimit + try? fetchedResultsController.performFetch() + } public init(managedObjectContext: NSManagedObjectContext) { self.fetchedResultsController = { @@ -36,6 +49,7 @@ final public class FeedFetchedResultsController: NSObject { fetchRequest.returnsObjectsAsFaults = false fetchRequest.shouldRefreshRefetchedObjects = true fetchRequest.fetchBatchSize = 15 + fetchRequest.fetchLimit = Constants.defaultFetchLimit let controller = NSFetchedResultsController( fetchRequest: fetchRequest, managedObjectContext: managedObjectContext,