From d5c98e651d8b72c64059a016af23bc0c78299153 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 16 Nov 2023 08:51:44 +1300 Subject: [PATCH 1/6] Use PostRepository to fetch posts in Posts List and Pages List --- .../Classes/Services/PostRepository.swift | 4 +- .../Pages/PageListViewController.swift | 23 +++++ .../Post/AbstractPostListViewController.swift | 95 +++++++++---------- 3 files changed, 70 insertions(+), 52 deletions(-) diff --git a/WordPress/Classes/Services/PostRepository.swift b/WordPress/Classes/Services/PostRepository.swift index 7f84cf44ab14..5b7679920c1a 100644 --- a/WordPress/Classes/Services/PostRepository.swift +++ b/WordPress/Classes/Services/PostRepository.swift @@ -390,7 +390,7 @@ extension PostRepository { /// - statuses: Only fetch pages whose status is included in the given statues. /// - blogID: Object ID of the site. /// - Returns: A `Task` instance representing the fetching. The fetch pages API requests will stop if the task is cancelled. - func fetchAllPages(statuses: [BasePost.Status], in blogID: TaggedManagedObjectID) -> Task<[TaggedManagedObjectID], Swift.Error> { + func fetchAllPages(statuses: [BasePost.Status], authorUserID: NSNumber? = nil, in blogID: TaggedManagedObjectID) -> Task<[TaggedManagedObjectID], Swift.Error> { Task { let pageSize = 100 var allPages = [TaggedManagedObjectID]() @@ -401,7 +401,7 @@ extension PostRepository { let current = try await fetch( type: Page.self, statuses: statuses, - authorUserID: nil, + authorUserID: authorUserID, range: pageRange, deleteOtherLocalPosts: false, in: blogID diff --git a/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift b/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift index 566f3948dbbe..6cdf9772e819 100644 --- a/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift +++ b/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift @@ -51,6 +51,8 @@ final class PageListViewController: AbstractPostListViewController, UIViewContro private var pages: [Page] = [] + private var fetchAllPagesTask: Task<[TaggedManagedObjectID], Error>? + // MARK: - Convenience constructors @objc class func controllerWithBlog(_ blog: Blog) -> PageListViewController { @@ -131,6 +133,11 @@ final class PageListViewController: AbstractPostListViewController, UIViewContro override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) QuickStartTourGuide.shared.endCurrentTour() + + if self.isMovingFromParent { + fetchAllPagesTask?.cancel() + fetchAllPagesTask = nil + } } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -165,6 +172,22 @@ final class PageListViewController: AbstractPostListViewController, UIViewContro return .page } + @MainActor + override func syncPosts(isFirstPage: Bool) async throws -> ([AbstractPost], Bool) { + let coreDataStack = ContextManager.shared + let filter = filterSettings.currentPostListFilter() + let author = filterSettings.shouldShowOnlyMyPosts() ? blogUserID() : nil + let blogID = TaggedManagedObjectID(blog) + + let repository = PostRepository(coreDataStack: coreDataStack) + let task = repository.fetchAllPages(statuses: filter.statuses, authorUserID: author, in: blogID) + self.fetchAllPagesTask = task + + let posts = try await task.value.map { try coreDataStack.mainContext.existingObject(with: $0) } + + return (posts, false) + } + override func syncHelper(_ syncHelper: WPContentSyncHelper, syncContentWithUserInteraction userInteraction: Bool, success: ((Bool) -> ())?, failure: ((NSError) -> ())?) { // The success and failure blocks are called in the parent class `AbstractPostListViewController` by the `syncPosts` method. Since that class is // used by both this one and the "Posts" screen, making changes to the sync helper is tough. To get around that, we make the fetch settings call diff --git a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift index 81f49ca9346b..041f3329968d 100644 --- a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift @@ -449,7 +449,7 @@ class AbstractPostListViewController: UIViewController, refreshResults() } - @objc func updateFilter(_ filter: PostListFilter, withSyncedPosts posts: [AbstractPost], syncOptions options: PostServiceSyncOptions) { + @objc func updateFilter(_ filter: PostListFilter, withSyncedPosts posts: [AbstractPost], hasMore: Bool) { guard posts.count > 0 else { assertionFailure("This method should not be called with no posts.") return @@ -459,7 +459,7 @@ class AbstractPostListViewController: UIViewController, // differing time offsets in the created dates. let oldestPost = posts.min { ($0.date_created_gmt ?? .distantPast) < ($1.date_created_gmt ?? .distantPast) } filter.oldestPostDate = oldestPost?.date_created_gmt - filter.hasMore = posts.count >= options.number.intValue + filter.hasMore = hasMore updateAndPerformFetchRequestRefreshingResults() } @@ -475,85 +475,80 @@ class AbstractPostListViewController: UIViewController, return .any } - func syncHelper(_ syncHelper: WPContentSyncHelper, syncContentWithUserInteraction userInteraction: Bool, success: ((_ hasMore: Bool) -> ())?, failure: ((_ error: NSError) -> ())?) { + @MainActor + func syncPosts(isFirstPage: Bool) async throws -> ([AbstractPost], Bool) { let postType = postTypeToSync() let filter = filterSettings.currentPostListFilter() let author = filterSettings.shouldShowOnlyMyPosts() ? blogUserID() : nil - let postService = PostService(managedObjectContext: managedObjectContext()) - - let options = PostServiceSyncOptions() - options.statuses = filter.statuses.strings - options.authorID = author - options.number = numberOfLoadedElement - options.purgesLocalSync = true - - postService.syncPosts( - ofType: postType, - with: options, - for: blog, - success: { [weak self] posts in - guard let self, let posts else { - return - } + let coreDataStack = ContextManager.shared + let blogID = TaggedManagedObjectID(blog) + let number = numberOfLoadedElement.intValue + + let repository = PostRepository(coreDataStack: coreDataStack) + let result = try await repository.paginate( + type: postType == .post ? Post.self : Page.self, + statuses: filter.statuses, + authorUserID: author, + offset: isFirstPage ? 0 : fetchResultsController.fetchedObjects?.count ?? 0, + number: number, + in: blogID + ) + + let posts = try result.map { try coreDataStack.mainContext.existingObject(with: $0) } + let hasMore = result.count >= number + + return (posts, hasMore) + } + + func syncHelper(_ syncHelper: WPContentSyncHelper, syncContentWithUserInteraction userInteraction: Bool, success: ((_ hasMore: Bool) -> ())?, failure: ((_ error: NSError) -> ())?) { + let filter = filterSettings.currentPostListFilter() + Task { @MainActor [weak self] in + do { + guard let (posts, hasMore) = try await self?.syncPosts(isFirstPage: true) else { return } + + guard let self else { return } if posts.count > 0 { - self.updateFilter(filter, withSyncedPosts: posts, syncOptions: options) + self.updateFilter(filter, withSyncedPosts: posts, hasMore: hasMore) SearchManager.shared.indexItems(posts) } success?(filter.hasMore) - }, failure: { [weak self] error in - - guard let self, let error else { - return - } + } catch { + guard let self else { return } failure?(error as NSError) if userInteraction == true { self.handleSyncFailure(error as NSError) } - }) + } + } } func syncHelper(_ syncHelper: WPContentSyncHelper, syncMoreWithSuccess success: ((_ hasMore: Bool) -> Void)?, failure: ((_ error: NSError) -> Void)?) { setFooterHidden(false) - let postType = postTypeToSync() let filter = filterSettings.currentPostListFilter() - let author = filterSettings.shouldShowOnlyMyPosts() ? blogUserID() : nil - let postService = PostService(managedObjectContext: managedObjectContext()) - - let options = PostServiceSyncOptions() - options.statuses = filter.statuses.strings - options.authorID = author - options.number = numberOfLoadedElement - options.offset = fetchResultsController.fetchedObjects?.count as NSNumber? - - postService.syncPosts( - ofType: postType, - with: options, - for: blog, - success: { [weak self] posts in - guard let self, let posts else { - return - } + Task { @MainActor [weak self] in + do { + guard let (posts, hasMore) = try await self?.syncPosts(isFirstPage: false) else { return } + + guard let self else { return } if posts.count > 0 { - self.updateFilter(filter, withSyncedPosts: posts, syncOptions: options) + self.updateFilter(filter, withSyncedPosts: posts, hasMore: hasMore) SearchManager.shared.indexItems(posts) } success?(filter.hasMore) - }, failure: { error in - guard let error else { - return - } + } catch { failure?(error as NSError) - }) + } + } } func syncContentStart(_ syncHelper: WPContentSyncHelper) { From e2f41729473e55d4a9b441c500722ec46b956aca Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 22 Nov 2023 11:28:36 +1300 Subject: [PATCH 2/6] Decouple PageTree from specific managed object context --- WordPress/Classes/Utility/PageTree.swift | 59 ++++++++++++-------- WordPress/WordPressTest/PagesListTests.swift | 26 ++++----- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/WordPress/Classes/Utility/PageTree.swift b/WordPress/Classes/Utility/PageTree.swift index 4cccbacb6494..ea0a58fa91b4 100644 --- a/WordPress/Classes/Utility/PageTree.swift +++ b/WordPress/Classes/Utility/PageTree.swift @@ -2,12 +2,18 @@ final class PageTree { // A node in a tree, which of course is also a tree itself. private class TreeNode { - let page: Page + struct PageData { + var postID: NSNumber? + var parentID: NSNumber? + } + let pageID: TaggedManagedObjectID + let pageData: PageData var children = [TreeNode]() var parentNode: TreeNode? init(page: Page, children: [TreeNode] = [], parentNode: TreeNode? = nil) { - self.page = page + self.pageID = TaggedManagedObjectID(page) + self.pageData = PageData(postID: page.postID, parentID: page.parentID) self.children = children self.parentNode = parentNode } @@ -15,15 +21,16 @@ final class PageTree { // The `PageTree` type is used to loaded // Some page There are pages They are pages that doesn't belong to the root level, but their parent pages haven't been loaded yet. var isOrphan: Bool { - (page.parentID?.int64Value ?? 0) > 0 && parentNode == nil + (pageData.parentID?.int64Value ?? 0) > 0 && parentNode == nil } - func dfsList() -> [Page] { + func dfsList(in context: NSManagedObjectContext) throws -> [Page] { var pages = [Page]() - _ = depthFirstSearch { level, node in - node.page.hierarchyIndex = level - node.page.hasVisibleParent = node.parentNode != nil - pages.append(node.page) + _ = try depthFirstSearch { level, node in + let page = try context.existingObject(with: node.pageID) + page.hierarchyIndex = level + page.hasVisibleParent = node.parentNode != nil + pages.append(page) return false } return pages @@ -35,18 +42,18 @@ final class PageTree { /// a boolean value indicate whether the search should be stopped. /// - Returns: `true` if search has been stopped by the closure. @discardableResult - func depthFirstSearch(using closure: (Int, TreeNode) -> Bool) -> Bool { - depthFirstSearch(level: 0, using: closure) + func depthFirstSearch(using closure: (Int, TreeNode) throws -> Bool) rethrows -> Bool { + try depthFirstSearch(level: 0, using: closure) } - private func depthFirstSearch(level: Int, using closure: (Int, TreeNode) -> Bool) -> Bool { - let shouldStop = closure(level, self) + private func depthFirstSearch(level: Int, using closure: (Int, TreeNode) throws -> Bool) rethrows -> Bool { + let shouldStop = try closure(level, self) if shouldStop { return true } for child in children { - let shouldStop = child.depthFirstSearch(level: level + 1, using: closure) + let shouldStop = try child.depthFirstSearch(level: level + 1, using: closure) if shouldStop { return true } @@ -77,7 +84,7 @@ final class PageTree { assert(parentID != 0) return depthFirstSearch { _, node in - if node.page.postID == parentID { + if node.pageData.postID == parentID { node.children.append(contentsOf: newNodes) newNodes.forEach { $0.parentNode = node } return true @@ -136,7 +143,7 @@ final class PageTree { nodes.remove(atOffsets: relocated) orphanNodes = nodes.enumerated().reduce(into: [:]) { indexes, node in if node.element.isOrphan { - let parentID = node.element.page.parentID ?? 0 + let parentID = node.element.pageData.parentID ?? 0 indexes[parentID, default: []].append(node.offset) } } @@ -145,7 +152,7 @@ final class PageTree { private func add(_ newNodes: [TreeNode]) { newNodes.forEach { newNode in - let parentID = newNode.page.parentID ?? 0 + let parentID = newNode.pageData.parentID ?? 0 // If the new node is at the root level, then simply add it as a child. if parentID == 0 { @@ -170,14 +177,14 @@ final class PageTree { /// Move all the nodes in the given argument to the current page tree. private func merge(subtree: PageTree) { - var parentIDs = subtree.nodes.reduce(into: Set()) { $0.insert($1.page.parentID ?? 0) } + var parentIDs = subtree.nodes.reduce(into: Set()) { $0.insert($1.pageData.parentID ?? 0) } // No need to look for root level parentIDs.remove(0) // Look up parent nodes upfront, to avoid repeated iteration for each node in `subtree`. let parentNodes = findNodes(postIDs: parentIDs) subtree.nodes.forEach { newNode in - let parentID = newNode.page.parentID ?? 0 + let parentID = newNode.pageData.parentID ?? 0 // If the new node is at the root level, then simply add it as a child if parentID == 0 { @@ -214,7 +221,7 @@ final class PageTree { // Using BFS under the assumption that page tree in most sites is a shallow tree, where most pages are in top layers. child.breadthFirstSearch { node in - let postID = node.page.postID ?? 0 + let postID = node.pageData.postID ?? 0 let foundIndex = ids.firstIndex(of: postID) if let foundIndex { ids.remove(at: foundIndex) @@ -227,15 +234,19 @@ final class PageTree { return result } - func hierarchyList() -> [Page] { - nodes.reduce(into: []) { - $0.append(contentsOf: $1.dfsList()) + func hierarchyList(in context: NSManagedObjectContext) throws -> [Page] { + try nodes.reduce(into: []) { + try $0.append(contentsOf: $1.dfsList(in: context)) } } - static func hierarchyList(of pages: [Page]) -> [Page] { + static func hierarchyList(of pages: [Page]) throws -> [Page] { + guard let context = pages.first?.managedObjectContext else { + return [] + } + let tree = PageTree() tree.add(pages) - return tree.hierarchyList() + return try tree.hierarchyList(in: context) } } diff --git a/WordPress/WordPressTest/PagesListTests.swift b/WordPress/WordPressTest/PagesListTests.swift index c82ced6b0efe..a3a9e124d6b3 100644 --- a/WordPress/WordPressTest/PagesListTests.swift +++ b/WordPress/WordPressTest/PagesListTests.swift @@ -14,12 +14,12 @@ class PagesListTests: CoreDataTestCase { page.postID = NSNumber(value: id) return page } - makeAssertions(pages: pages) + try makeAssertions(pages: pages) } func testOneNestedList() throws { let pages = parentPage(childrenCount: 1, additionalLevels: 5) - makeAssertions(pages: pages) + try makeAssertions(pages: pages) } func testManyNestedLists() throws { @@ -35,7 +35,7 @@ class PagesListTests: CoreDataTestCase { groups[0][4].parentID = NSNumber(value: randomID.next()) let pages = groups.flatMap { $0 } - makeAssertions(pages: pages) + try makeAssertions(pages: pages) } func testHugeNestedLists() throws { @@ -50,7 +50,7 @@ class PagesListTests: CoreDataTestCase { pages.append(contentsOf: newPages) } - makeAssertions(pages: pages) + try makeAssertions(pages: pages) } // Measure performance using a page list where somewhat reflects a real-world page list, where some pages whose parent pages are in list. @@ -72,9 +72,7 @@ class PagesListTests: CoreDataTestCase { NSLog("\(pages.count) pages used in \(#function)") measure { - let pageTree = PageTree() - pageTree.add(pages) - let list = pageTree.hierarchyList() + let list = (try? PageTree.hierarchyList(of: pages)) ?? [] XCTAssertEqual(list.count, pages.count) } } @@ -91,9 +89,7 @@ class PagesListTests: CoreDataTestCase { NSLog("\(pages.count) pages used in \(#function)") measure { - let pageTree = PageTree() - pageTree.add(pages) - let list = pageTree.hierarchyList() + let list = (try? PageTree.hierarchyList(of: pages)) ?? [] XCTAssertEqual(list.count, pages.count) } } @@ -109,13 +105,13 @@ class PagesListTests: CoreDataTestCase { orphan1[0].parentID = 100000 orphan2[0].parentID = 200000 - makeAssertions(pages: pages) + try makeAssertions(pages: pages) } func testHierachyListRepresentationRoundtrip() throws { let roundtrip: (String) throws -> Void = { string in let pages = try Array(hierarchyListRepresentation: string, context: self.mainContext) - XCTAssertEqual(PageTree.hierarchyList(of: pages).hierarchyListRepresentation(), string) + try XCTAssertEqual(PageTree.hierarchyList(of: pages).hierarchyListRepresentation(), string) } try roundtrip(""" @@ -164,7 +160,7 @@ class PagesListTests: CoreDataTestCase { return pages } - private func makeAssertions(pages: [Page], file: StaticString = #file, line: UInt = #line) { + private func makeAssertions(pages: [Page], file: StaticString = #file, line: UInt = #line) throws { var start: CFAbsoluteTime start = CFAbsoluteTimeGetCurrent() @@ -172,9 +168,7 @@ class PagesListTests: CoreDataTestCase { NSLog("hierarchySort took \(String(format: "%.3f", (CFAbsoluteTimeGetCurrent() - start) * 1000)) millisecond to process \(pages.count) pages") start = CFAbsoluteTimeGetCurrent() - let pageTree = PageTree() - pageTree.add(pages) - let new = pageTree.hierarchyList() + let new = try PageTree.hierarchyList(of: pages) NSLog("PageTree took \(String(format: "%.3f", (CFAbsoluteTimeGetCurrent() - start) * 1000)) millisecond to process \(pages.count) pages") start = CFAbsoluteTimeGetCurrent() From eef9c218a2cfe5fc113e51adc271917798d2b216 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 22 Nov 2023 14:02:59 +1300 Subject: [PATCH 3/6] Use PageTree in Pages List --- .../Pages/PageListViewController+Menu.swift | 4 +- .../Pages/PageListViewController.swift | 58 +++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Pages/PageListViewController+Menu.swift b/WordPress/Classes/ViewRelated/Pages/PageListViewController+Menu.swift index 8df28cbc6ca6..82b74b55fedd 100644 --- a/WordPress/Classes/ViewRelated/Pages/PageListViewController+Menu.swift +++ b/WordPress/Classes/ViewRelated/Pages/PageListViewController+Menu.swift @@ -66,7 +66,9 @@ extension PageListViewController: InteractivePostViewDelegate { func setParent(for apost: AbstractPost) { guard let page = apost as? Page else { return } - setParentPage(for: page) + Task { + await setParentPage(for: page) + } } func setHomepage(for apost: AbstractPost) { diff --git a/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift b/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift index 6cdf9772e819..6420fdcd10b4 100644 --- a/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift +++ b/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift @@ -233,17 +233,55 @@ final class PageListViewController: AbstractPostListViewController, UIViewContro override func updateAndPerformFetchRequest() { super.updateAndPerformFetchRequest() - reloadPages() + Task { + await reloadPagesAndUI() + } } - private func reloadPages() { + @MainActor + private func reloadPagesAndUI() async { let status = filterSettings.currentPostListFilter().filterType let pages = (fetchResultsController.fetchedObjects ?? []) as! [Page] + if status == .published { - self.pages = pages.setHomePageFirst().hierarchySort() + let coreDataStack = ContextManager.shared + let pageIDs = pages.map { TaggedManagedObjectID($0) } + + do { + self.pages = try await buildPageTree(pageIDs: pageIDs) + .hierarchyList(in: coreDataStack.mainContext) + } catch { + DDLogError("Failed to reload published pages: \(error)") + } } else { self.pages = pages } + + tableView.reloadData() + refreshResults() + } + + /// Build page hierachy in background, which should not take long (less than 2 seconds for 6000+ pages). + @MainActor + func buildPageTree(pageIDs: [TaggedManagedObjectID]? = nil, request: NSFetchRequest? = nil) async throws -> PageTree { + assert(pageIDs != nil || request != nil, "`pageIDs` and `request` can not both be nil") + + let coreDataStack = ContextManager.shared + return try await coreDataStack.performQuery { context in + var pages = [Page]() + + if let pageIDs { + pages = try pageIDs.map(context.existingObject(with:)) + } else if let request { + pages = try context.fetch(request) + } + + pages = pages.setHomePageFirst() + + let tree = PageTree() + tree.add(pages) + return tree + } } override func syncContentEnded(_ syncHelper: WPContentSyncHelper) { @@ -264,9 +302,9 @@ final class PageListViewController: AbstractPostListViewController, UIViewContro } override func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - reloadPages() - tableView.reloadData() - refreshResults() + Task { + await reloadPagesAndUI() + } } // MARK: - Core Data @@ -417,16 +455,14 @@ final class PageListViewController: AbstractPostListViewController, UIViewContro // MARK: - Cell Action Handling - func setParentPage(for page: Page) { + @MainActor + func setParentPage(for page: Page) async { let request = NSFetchRequest(entityName: Page.entityName()) let filter = PostListFilter.publishedFilter() request.predicate = filter.predicate(for: blog, author: .everyone) request.sortDescriptors = filter.sortDescriptors do { - var pages = try managedObjectContext() - .fetch(request) - .setHomePageFirst() - .hierarchySort() + var pages = try await buildPageTree(request: request).hierarchyList(in: ContextManager.shared.mainContext) if let index = pages.firstIndex(of: page) { pages = pages.remove(from: index) } From 1a32c7ad57fec2e451735f5550a36800db760da4 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 22 Nov 2023 14:50:45 +1300 Subject: [PATCH 4/6] Add a release note --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 78d6866292de..94c6d1dea0e0 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,6 +5,7 @@ * [*] [internal] Fix an issue with scheduling of posts not working on iOS 17 with Xcode 15 [#22012] * [*] [internal] Remove SDWebImage dependency from the app and improve cache cost calculation for GIFs [#21285] * [*] Stats: Fix an issue where sites for clicked URLs do not open [#22061] +* [*] Improve pages list performance when there are hundreds of pages in the site [#22070] 23.7 ----- From 5981dcff18b7e203f61deb654cfb57ac2f443e82 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Mon, 27 Nov 2023 13:36:17 +1300 Subject: [PATCH 5/6] Put sync posts result into a named tuple --- .../Classes/ViewRelated/Pages/PageListViewController.swift | 2 +- .../ViewRelated/Post/AbstractPostListViewController.swift | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift b/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift index 6420fdcd10b4..dac04f114c0b 100644 --- a/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift +++ b/WordPress/Classes/ViewRelated/Pages/PageListViewController.swift @@ -173,7 +173,7 @@ final class PageListViewController: AbstractPostListViewController, UIViewContro } @MainActor - override func syncPosts(isFirstPage: Bool) async throws -> ([AbstractPost], Bool) { + override func syncPosts(isFirstPage: Bool) async throws -> SyncPostResult { let coreDataStack = ContextManager.shared let filter = filterSettings.currentPostListFilter() let author = filterSettings.shouldShowOnlyMyPosts() ? blogUserID() : nil diff --git a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift index 041f3329968d..6280539e3c12 100644 --- a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift @@ -13,6 +13,8 @@ class AbstractPostListViewController: UIViewController, UITableViewDataSource, NetworkAwareUI // This protocol is not in an extension so that subclasses can override noConnectionMessage() { + typealias SyncPostResult = (posts: [AbstractPost], hasMore: Bool) + private static let postsControllerRefreshInterval = TimeInterval(300) private static let httpErrorCodeForbidden = 403 private static let postsFetchRequestBatchSize = 10 @@ -476,7 +478,7 @@ class AbstractPostListViewController: UIViewController, } @MainActor - func syncPosts(isFirstPage: Bool) async throws -> ([AbstractPost], Bool) { + func syncPosts(isFirstPage: Bool) async throws -> SyncPostResult { let postType = postTypeToSync() let filter = filterSettings.currentPostListFilter() let author = filterSettings.shouldShowOnlyMyPosts() ? blogUserID() : nil From 19307ccad85c448547c6a84987894cdaecfd78fb Mon Sep 17 00:00:00 2001 From: Tony Li Date: Mon, 27 Nov 2023 13:36:48 +1300 Subject: [PATCH 6/6] Add a code comment --- .../ViewRelated/Post/AbstractPostListViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift index 6280539e3c12..63523783be00 100644 --- a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift @@ -539,6 +539,7 @@ class AbstractPostListViewController: UIViewController, do { guard let (posts, hasMore) = try await self?.syncPosts(isFirstPage: false) else { return } + // User may have exit the screen when the "syncPosts" call above completes. guard let self else { return } if posts.count > 0 {