frenzy-ios/Reader/Screens/Home/HomeCollectionViewCell.swift
Shadowfacts 6881441671 Don't use shared background context for fetching item counts
A significant fraction of the time was spent waiting for the background
context to be available, before the count could even be started. Since
the counts don't need to use the shared background context, let them
each use their own context to avoid contention.
2022-09-10 14:21:30 -04:00

76 lines
2.9 KiB
Swift

//
// HomeCollectionViewCell.swift
// Reader
//
// Created by Shadowfacts on 1/9/22.
//
import UIKit
import Fervor
import Persistence
import OSLog
private let signposter = OSSignposter(subsystem: "net.shadowfacts.Reader", category: "HomeCollectionViewCell")
class HomeCollectionViewCell: UICollectionViewListCell {
private var currentItemCountTask: Task<Void, Never>?
private var itemCount: Int?
#if !targetEnvironment(macCatalyst)
override func updateConfiguration(using state: UICellConfigurationState) {
var backgroundConfig = UIBackgroundConfiguration.listGroupedCell().updated(for: state)
if state.isHighlighted || state.isSelected {
backgroundConfig.backgroundColor = .appCellHighlightBackground
} else {
backgroundConfig.backgroundColor = .appBackground
}
self.backgroundConfiguration = backgroundConfig
}
#endif
override func prepareForReuse() {
super.prepareForReuse()
currentItemCountTask?.cancel()
itemCount = nil
}
func updateUI(item: ItemListType, persistentContainer: PersistentContainer) {
var config = UIListContentConfiguration.valueCell()
config.text = item.title
if let itemCount {
config.secondaryText = itemCount.formatted(.number)
}
config.secondaryTextProperties.color = .tintColor
self.contentConfiguration = config
currentItemCountTask = Task(priority: .userInitiated) {
let state = signposter.beginInterval("fetch count", id: signposter.makeSignpostID(), "\(String(item.hashValue, radix: 16), privacy: .public)")
if let count = await fetchCount(item: item, in: persistentContainer),
!Task.isCancelled {
self.itemCount = count
config.secondaryText = count.formatted(.number)
self.contentConfiguration = config
}
signposter.endInterval("fetch count", state)
}
}
private func fetchCount(item: ItemListType, in persistentContainer: PersistentContainer) async -> Int? {
guard let request = item.countFetchRequest else {
return nil
}
return await withCheckedContinuation({ continuation in
let state = signposter.beginInterval("waiting to perform", id: signposter.makeSignpostID(), "\(String(item.hashValue, radix: 16), privacy: .public)")
persistentContainer.performBackgroundTask { context in
signposter.endInterval("waiting to perform", state)
let state = signposter.beginInterval("count", id: signposter.makeSignpostID(), "\(String(item.hashValue, radix: 16), privacy: .public)")
let count = try? context.count(for: request)
signposter.endInterval("count", state)
continuation.resume(returning: count)
}
})
}
}