Fix potential race condition with DiffableTimelineLikeTableViewController

This commit is contained in:
Shadowfacts 2021-08-15 18:44:23 -04:00
parent f109253bba
commit c0097ba752
2 changed files with 25 additions and 21 deletions

View File

@ -168,7 +168,7 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
} }
} }
override func loadOlderItems(currentSnapshot: Snapshot, completion: @escaping (LoadResult) -> Void) { override func loadOlderItems(currentSnapshot: @escaping () -> Snapshot, completion: @escaping (LoadResult) -> Void) {
guard let older = older else { guard let older = older else {
completion(.failure(.noOlder)) completion(.failure(.noOlder))
return return
@ -176,12 +176,12 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
if #available(iOS 15.0, *), if #available(iOS 15.0, *),
Preferences.shared.disableInfiniteScrolling && !didConfirmLoadMore { Preferences.shared.disableInfiniteScrolling && !didConfirmLoadMore {
guard !currentSnapshot.itemIdentifiers(inSection: .footer).contains(.confirmLoadMore) else { var snapshot = currentSnapshot()
guard !snapshot.itemIdentifiers(inSection: .footer).contains(.confirmLoadMore) else {
// todo: need something more accurate than "success"/"failure" // todo: need something more accurate than "success"/"failure"
completion(.success(currentSnapshot)) completion(.success(snapshot))
return return
} }
var snapshot = currentSnapshot
snapshot.appendItems([.confirmLoadMore], toSection: .footer) snapshot.appendItems([.confirmLoadMore], toSection: .footer)
self.dataSource.apply(snapshot) self.dataSource.apply(snapshot)
completion(.success(snapshot)) completion(.success(snapshot))
@ -199,7 +199,7 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
self.older = pagination?.older self.older = pagination?.older
self.mastodonController.persistentContainer.addAll(statuses: statuses) { self.mastodonController.persistentContainer.addAll(statuses: statuses) {
var snapshot = currentSnapshot var snapshot = currentSnapshot()
snapshot.appendItems(statuses.map { .status(id: $0.id, state: .unknown) }, toSection: .statuses) snapshot.appendItems(statuses.map { .status(id: $0.id, state: .unknown) }, toSection: .statuses)
snapshot.deleteItems([.confirmLoadMore]) snapshot.deleteItems([.confirmLoadMore])
completion(.success(snapshot)) completion(.success(snapshot))
@ -208,7 +208,7 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
} }
} }
override func loadNewerItems(currentSnapshot: Snapshot, completion: @escaping (LoadResult) -> Void) { override func loadNewerItems(currentSnapshot: @escaping () -> Snapshot, completion: @escaping (LoadResult) -> Void) {
guard let newer = newer else { guard let newer = newer else {
completion(.failure(.noNewer)) completion(.failure(.noNewer))
return return
@ -228,7 +228,7 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
} }
self.mastodonController.persistentContainer.addAll(statuses: statuses) { self.mastodonController.persistentContainer.addAll(statuses: statuses) {
var snapshot = currentSnapshot var snapshot = currentSnapshot()
let newIdentifiers = statuses.map { Item.status(id: $0.id, state: .unknown) } let newIdentifiers = statuses.map { Item.status(id: $0.id, state: .unknown) }
if let first = snapshot.itemIdentifiers(inSection: .statuses).first { if let first = snapshot.itemIdentifiers(inSection: .statuses).first {
snapshot.insertItems(newIdentifiers, beforeItem: first) snapshot.insertItems(newIdentifiers, beforeItem: first)

View File

@ -146,7 +146,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
state = .loadingOlder state = .loadingOlder
loadOlderItems(currentSnapshot: dataSource.snapshot()) { result in loadOlderItems(currentSnapshot: dataSource.snapshot) { result in
DispatchQueue.main.async { DispatchQueue.main.async {
self.state = .loaded self.state = .loaded
@ -212,18 +212,22 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
state = .loadingNewer state = .loadingNewer
let snapshot = dataSource.snapshot() var firstItem: Item? = nil
let currentSnapshot: () -> Snapshot = {
let snapshot = self.dataSource.snapshot()
var item: Item? = nil for section in self.timelineContentSections() {
for section in timelineContentSections() {
if snapshot.indexOfSection(section) != nil, if snapshot.indexOfSection(section) != nil,
let first = snapshot.itemIdentifiers(inSection: section).first { let first = snapshot.itemIdentifiers(inSection: section).first {
item = first firstItem = first
break break
} }
} }
loadNewerItems(currentSnapshot: snapshot) { result in return snapshot
}
loadNewerItems(currentSnapshot: currentSnapshot) { result in
DispatchQueue.main.async { DispatchQueue.main.async {
self.refreshControl?.endRefreshing() self.refreshControl?.endRefreshing()
self.state = .loaded self.state = .loaded
@ -231,8 +235,8 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
switch result { switch result {
case let .success(snapshot): case let .success(snapshot):
self.dataSource.apply(snapshot, animatingDifferences: false) self.dataSource.apply(snapshot, animatingDifferences: false)
if let item = item, if let firstItem = firstItem,
let indexPath = self.dataSource.indexPath(for: item) { let indexPath = self.dataSource.indexPath(for: firstItem) {
// maintain the current position in the list (don't scroll to top) // maintain the current position in the list (don't scroll to top)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false) self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
} }
@ -265,11 +269,11 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
fatalError("loadInitialItems(completion:) must be implemented by subclasses") fatalError("loadInitialItems(completion:) must be implemented by subclasses")
} }
func loadOlderItems(currentSnapshot: Snapshot, completion: @escaping (LoadResult) -> Void) { func loadOlderItems(currentSnapshot: @escaping () -> Snapshot, completion: @escaping (LoadResult) -> Void) {
fatalError("loadOlderItesm(completion:) must be implemented by subclasses") fatalError("loadOlderItesm(completion:) must be implemented by subclasses")
} }
func loadNewerItems(currentSnapshot: Snapshot, completion: @escaping (LoadResult) -> Void) { func loadNewerItems(currentSnapshot: @escaping () -> Snapshot, completion: @escaping (LoadResult) -> Void) {
fatalError("loadNewerItems(completion:) must be implemented by subclasses") fatalError("loadNewerItems(completion:) must be implemented by subclasses")
} }