Fix numerous concurrency warnings.

This commit is contained in:
Brent Simmons 2024-03-23 12:20:32 -07:00
parent acd86c9e2a
commit 177d660cff
10 changed files with 81 additions and 58 deletions

View File

@ -209,15 +209,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
let dataFolder: String
let database: ArticlesDatabase
var delegate: AccountDelegate
static let saveQueue = CoalescingQueue(name: "Account Save Queue", interval: 1.0)
@MainActor static let saveQueue = CoalescingQueue(name: "Account Save Queue", interval: 1.0)
private var unreadCounts = [String: Int]() // [feedID: Int]
private var _flattenedFeeds = Set<Feed>()
private var flattenedFeedsNeedUpdate = true
private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self)
@MainActor private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
@MainActor private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self)
var metadata = AccountMetadata() {
didSet {
delegate.accountMetadata = metadata
@ -248,7 +248,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
else {
NotificationCenter.default.post(name: .AccountRefreshDidFinish, object: self)
opmlFile.markAsDirty()
Task { @MainActor in
opmlFile.markAsDirty()
}
}
}
}
@ -258,7 +260,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return delegate.refreshProgress
}
init(dataFolder: String, type: AccountType, accountID: String, secretsProvider: SecretsProvider, transport: Transport? = nil) {
@MainActor init(dataFolder: String, type: AccountType, accountID: String, secretsProvider: SecretsProvider, transport: Transport? = nil) {
switch type {
case .onMyMac:
self.delegate = LocalAccountDelegate()
@ -480,9 +482,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
public func save() {
metadataFile.save()
feedMetadataFile.save()
opmlFile.save()
Task { @MainActor in
metadataFile.save()
feedMetadataFile.save()
opmlFile.save()
}
}
public func prepareForDeletion() {
@ -775,9 +779,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func structureDidChange() {
// Feeds were added or deleted. Or folders added or deleted.
// Or feeds inside folders were added or deleted.
opmlFile.markAsDirty()
flattenedFeedsNeedUpdate = true
feedDictionariesNeedUpdate = true
Task { @MainActor in
opmlFile.markAsDirty()
flattenedFeedsNeedUpdate = true
feedDictionariesNeedUpdate = true
}
}
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
@ -1059,7 +1065,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
extension Account: AccountMetadataDelegate {
func valueDidChange(_ accountMetadata: AccountMetadata, key: AccountMetadata.CodingKeys) {
metadataFile.markAsDirty()
Task { @MainActor in
metadataFile.markAsDirty()
}
}
}
@ -1068,11 +1076,14 @@ extension Account: AccountMetadataDelegate {
extension Account: FeedMetadataDelegate {
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
feedMetadataFile.markAsDirty()
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
return
Task { @MainActor in
feedMetadataFile.markAsDirty()
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
return
}
feed.postFeedSettingDidChangeNotification(key)
}
feed.postFeedSettingDidChangeNotification(key)
}
}

View File

@ -96,8 +96,8 @@ public final class AccountManager: UnreadCountProvider {
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
}
public init(accountsFolder: String, secretsProvider: SecretsProvider) {
@MainActor public init(accountsFolder: String, secretsProvider: SecretsProvider) {
self.accountsFolder = accountsFolder
self.secretsProvider = secretsProvider
@ -127,7 +127,7 @@ public final class AccountManager: UnreadCountProvider {
// MARK: - API
public func createAccount(type: AccountType) -> Account {
@MainActor public func createAccount(type: AccountType) -> Account {
let accountID = type == .cloudKit ? "iCloud" : UUID().uuidString
let accountFolder = (accountsFolder as NSString).appendingPathComponent("\(type.rawValue)_\(accountID)")
@ -438,11 +438,11 @@ private extension AccountManager {
unreadCount = calculateUnreadCount(activeAccounts)
}
func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
@MainActor func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
return Account(dataFolder: accountSpecifier.folderPath, type: accountSpecifier.type, accountID: accountSpecifier.identifier, secretsProvider: secretsProvider)
}
func loadAccount(_ filename: String) -> Account? {
@MainActor func loadAccount(_ filename: String) -> Account? {
let folderPath = (accountsFolder as NSString).appendingPathComponent(filename)
if let accountSpecifier = AccountSpecifier(folderPath: folderPath) {
return loadAccount(accountSpecifier)
@ -450,7 +450,7 @@ private extension AccountManager {
return nil
}
func readAccountsFromDisk() {
@MainActor func readAccountsFromDisk() {
var filenames: [String]?
do {

View File

@ -10,8 +10,8 @@ import Foundation
import os.log
import Core
final class AccountMetadataFile {
@MainActor final class AccountMetadataFile {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "accountMetadataFile")
private let fileURL: URL

View File

@ -17,19 +17,19 @@ final class FeedMetadataFile {
private let fileURL: URL
private let account: Account
private var isDirty = false {
@MainActor private var isDirty = false {
didSet {
queueSaveToDiskIfNeeded()
}
}
private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
@MainActor private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
init(filename: String, account: Account) {
self.fileURL = URL(fileURLWithPath: filename)
self.account = account
}
func markAsDirty() {
@MainActor func markAsDirty() {
isDirty = true
}
@ -61,11 +61,11 @@ final class FeedMetadataFile {
private extension FeedMetadataFile {
func queueSaveToDiskIfNeeded() {
@MainActor func queueSaveToDiskIfNeeded() {
saveQueue.add(self, #selector(saveToDiskIfNeeded))
}
@objc func saveToDiskIfNeeded() {
@MainActor @objc func saveToDiskIfNeeded() {
if isDirty {
isDirty = false
save()

View File

@ -11,8 +11,8 @@ import os.log
import RSParser
import Core
final class OPMLFile {
@MainActor final class OPMLFile {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "opmlFile")
private let fileURL: URL

View File

@ -30,9 +30,9 @@ struct QueueCall: Equatable {
}
}
@objc public final class CoalescingQueue: NSObject {
@MainActor @objc public final class CoalescingQueue: NSObject {
public static let standard = CoalescingQueue(name: "Standard", interval: 0.05, maxInterval: 0.1)
@MainActor public static let standard = CoalescingQueue(name: "Standard", interval: 0.05, maxInterval: 0.1)
public let name: String
public var isPaused = false
private let interval: TimeInterval

View File

@ -278,11 +278,15 @@ private extension FaviconDownloader {
}
func queueSaveHomePageToFaviconURLCacheIfNeeded() {
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
Task { @MainActor in
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
}
}
func queueSaveHomePageURLsWithNoFaviconURLCacheIfNeeded() {
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
Task { @MainActor in
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
}
}
func saveHomePageToFaviconURLCache() {

View File

@ -18,7 +18,7 @@ extension Notification.Name {
static let FeedIconDidBecomeAvailable = Notification.Name("FeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
}
public final class FeedIconDownloader {
@MainActor public final class FeedIconDownloader {
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
@ -81,14 +81,16 @@ public final class FeedIconDownloader {
return IconImage.appIcon
}
func checkHomePageURL() {
@MainActor func checkHomePageURL() {
guard let homePageURL = feed.homePageURL else {
return
}
icon(forHomePageURL: homePageURL, feed: feed) { (image) in
if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image)
Task { @MainActor in
if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image)
}
}
}
}
@ -96,11 +98,13 @@ public final class FeedIconDownloader {
func checkFeedIconURL() {
if let iconURL = feed.iconURL {
icon(forURL: iconURL, feed: feed) { (image) in
if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image)
} else {
checkHomePageURL()
Task { @MainActor in
if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image)
} else {
checkHomePageURL()
}
}
}
} else {
@ -110,9 +114,11 @@ public final class FeedIconDownloader {
if let feedProviderURL = feedURLToIconURLCache[feed.url] {
self.icon(forURL: feedProviderURL, feed: feed) { (image) in
if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image)
Task { @MainActor in
if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image)
}
}
}
return nil
@ -153,7 +159,7 @@ public final class FeedIconDownloader {
private extension FeedIconDownloader {
func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @Sendable @escaping (RSImage?) -> Void) {
if homePagesWithNoIconURLCache.contains(homePageURL) || homePagesWithUglyIcons.contains(homePageURL) {
imageResultBlock(nil)
@ -168,7 +174,7 @@ private extension FeedIconDownloader {
findIconURLForHomePageURL(homePageURL, feed: feed)
}
func icon(forURL url: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
func icon(forURL url: String, feed: Feed, _ imageResultBlock: @Sendable @escaping (RSImage?) -> Void) {
waitingForFeedURLs[url] = feed
guard let imageData = imageDownloader.image(for: url) else {
imageResultBlock(nil)
@ -255,15 +261,15 @@ private extension FeedIconDownloader {
homePagesWithNoIconURLCache = Set(decoded)
}
func queueSaveFeedURLToIconURLCacheIfNeeded() {
@MainActor func queueSaveFeedURLToIconURLCacheIfNeeded() {
FeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded))
}
func queueSaveHomePageToIconURLCacheIfNeeded() {
@MainActor func queueSaveHomePageToIconURLCacheIfNeeded() {
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
}
func queueHomePagesWithNoIconURLCacheIfNeeded() {
@MainActor func queueHomePagesWithNoIconURLCacheIfNeeded() {
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
}

View File

@ -22,7 +22,7 @@ final class ExtensionContainersFile {
return containerURL!.appendingPathComponent("extension_containers.plist").path
}()
private var isDirty = false {
@MainActor private var isDirty = false {
didSet {
queueSaveToDiskIfNeeded()
}
@ -65,15 +65,15 @@ final class ExtensionContainersFile {
private extension ExtensionContainersFile {
@objc func markAsDirty() {
@MainActor @objc func markAsDirty() {
isDirty = true
}
func queueSaveToDiskIfNeeded() {
@MainActor func queueSaveToDiskIfNeeded() {
saveQueue.add(self, #selector(saveToDiskIfNeeded))
}
@objc func saveToDiskIfNeeded() {
@MainActor @objc func saveToDiskIfNeeded() {
if isDirty {
isDirty = false
save()

View File

@ -108,7 +108,9 @@ extension SmartFeed: ArticleFetcher {
private extension SmartFeed {
func queueFetchUnreadCounts() {
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
Task { @MainActor in
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
}
}
func fetchUnreadCount(for account: Account) {