Continue fixing concurrency warnings.

This commit is contained in:
Brent Simmons
2024-03-19 23:05:30 -07:00
parent 6ab10e871c
commit d0760f3d12
64 changed files with 444 additions and 459 deletions

View File

@@ -20,8 +20,8 @@ import UniformTypeIdentifiers
import CoreSpotlight
#endif
class ActivityManager {
@MainActor final class ActivityManager {
private var nextUnreadActivity: NSUserActivity?
private var selectingActivity: NSUserActivity?
private var readingActivity: NSUserActivity?
@@ -264,8 +264,8 @@ private extension ActivityManager {
return value?.components(separatedBy: " ").filter { $0.count > 2 } ?? []
}
func updateSelectingActivityFeedSearchAttributes(with feed: Feed) {
@MainActor func updateSelectingActivityFeedSearchAttributes(with feed: Feed) {
let attributeSet = CSSearchableItemAttributeSet(contentType: UTType.item)
attributeSet.title = feed.nameForDisplay
attributeSet.keywords = makeKeywords(feed.nameForDisplay)

View File

@@ -19,11 +19,12 @@ public enum ArticleExtractorState {
}
protocol ArticleExtractorDelegate {
func articleExtractionDidFail(with: Error)
func articleExtractionDidComplete(extractedArticle: ExtractedArticle)
@MainActor func articleExtractionDidFail(with: Error)
@MainActor func articleExtractionDidComplete(extractedArticle: ExtractedArticle)
}
class ArticleExtractor {
final class ArticleExtractor {
private var dataTask: URLSessionDataTask? = nil

View File

@@ -14,7 +14,7 @@ import RSCore
import Articles
import Account
struct ArticleRenderer {
@MainActor struct ArticleRenderer {
typealias Rendering = (style: String, html: String, title: String, baseURL: String)
@@ -30,10 +30,10 @@ struct ArticleRenderer {
}
}
static var imageIconScheme = "nnwImageIcon"
static var blank = Page(name: "blank")
static var page = Page(name: "page")
static let imageIconScheme = "nnwImageIcon"
static let blank = Page(name: "blank")
static let page = Page(name: "page")
private let article: Article?
private let extractedArticle: ExtractedArticle?
@@ -328,7 +328,7 @@ private extension ArticleRenderer {
// MARK: - Article extension
private extension Article {
@MainActor private extension Article {
var baseURL: URL? {
var s = link

View File

@@ -10,22 +10,20 @@ import Foundation
struct ArticlePathInfo {
let accountID: String
let articleID: String
let accountID: String?
let accountName: String?
let articleID: String?
let feedID: String?
init?(userInfo: [AnyHashable: Any]) {
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [String: String] else {
return nil
}
guard let accountID = articlePathUserInfo[ArticlePathKey.accountID] else {
return nil
}
guard let articleID = articlePathUserInfo[ArticlePathKey.articleID] else {
return nil
}
self.accountID = accountID
self.articleID = articleID
self.accountID = articlePathUserInfo[ArticlePathKey.accountID]
self.accountName = articlePathUserInfo[ArticlePathKey.accountName]
self.articleID = articlePathUserInfo[ArticlePathKey.articleID]
self.feedID = articlePathUserInfo[ArticlePathKey.feedID]
}
}

View File

@@ -12,7 +12,7 @@ import Articles
// Mark articles read/unread, starred/unstarred, deleted/undeleted.
final class MarkStatusCommand: UndoableCommand {
@MainActor final class MarkStatusCommand: UndoableCommand {
let undoActionName: String
let redoActionName: String
@@ -50,12 +50,12 @@ final class MarkStatusCommand: UndoableCommand {
self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, undoManager: undoManager, completion: completion)
}
func perform() {
@MainActor func perform() {
mark(statusKey, flag)
registerUndo()
}
func undo() {
@MainActor func undo() {
mark(statusKey, !flag)
registerRedo()
}
@@ -63,7 +63,7 @@ final class MarkStatusCommand: UndoableCommand {
private extension MarkStatusCommand {
func mark(_ statusKey: ArticleStatus.Key, _ flag: Bool) {
@MainActor func mark(_ statusKey: ArticleStatus.Key, _ flag: Bool) {
markArticles(articles, statusKey: statusKey, flag: flag, completion: completion)
completion = nil
}
@@ -83,7 +83,7 @@ private extension MarkStatusCommand {
}
}
static func filteredArticles(_ articles: [Article], _ statusKey: ArticleStatus.Key, _ flag: Bool) -> [Article] {
@MainActor static func filteredArticles(_ articles: [Article], _ statusKey: ArticleStatus.Key, _ flag: Bool) -> [Article] {
return articles.filter{ article in
guard article.status.boolStatus(forKey: statusKey) != flag else { return false }

View File

@@ -21,7 +21,7 @@ final class SendToMarsEditCommand: SendToCommand {
appToUse() != nil
}
func sendObject(_ object: Any?, selectedText: String?) {
@MainActor func sendObject(_ object: Any?, selectedText: String?) {
guard canSendObject(object, selectedText: selectedText) else {
return
@@ -39,7 +39,7 @@ final class SendToMarsEditCommand: SendToCommand {
private extension SendToMarsEditCommand {
func send(_ article: Article, to app: UserApp) {
@MainActor func send(_ article: Article, to app: UserApp) {
// App has already been launched.

View File

@@ -29,7 +29,7 @@ final class SendToMicroBlogCommand: SendToCommand {
return true
}
func sendObject(_ object: Any?, selectedText: String?) {
@MainActor func sendObject(_ object: Any?, selectedText: String?) {
guard canSendObject(object, selectedText: selectedText) else {
return
@@ -60,7 +60,7 @@ final class SendToMicroBlogCommand: SendToCommand {
private extension Article {
var attributionString: String {
@MainActor var attributionString: String {
// Feed name, or feed name + author name (if author is specified per-article).
// Includes trailing space.

View File

@@ -11,7 +11,7 @@ import Account
struct AddFeedDefaultContainer {
static var defaultContainer: Container? {
@MainActor static var defaultContainer: Container? {
if let accountID = AppDefaults.shared.addFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) {
if let folderName = AppDefaults.shared.addFeedFolderName, let folder = account.existingFolder(withDisplayName: folderName) {

View File

@@ -10,7 +10,7 @@ import Foundation
import Articles
import RSParser
struct ArticleStringFormatter {
@MainActor struct ArticleStringFormatter {
private static var feedNameCache = [String: String]()
private static var titleCache = [String: String]()

View File

@@ -13,7 +13,7 @@ import Account
// These handle multiple accounts.
func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
@MainActor func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
let d: [String: Set<Article>] = accountAndArticlesDictionary(articles)
@@ -42,7 +42,7 @@ private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String:
extension Article {
var feed: Feed? {
@MainActor var feed: Feed? {
return account?.existingFeed(withFeedID: feedID)
}
@@ -98,7 +98,7 @@ extension Article {
return datePublished ?? dateModified ?? status.dateArrived
}
var isAvailableToMarkUnread: Bool {
@MainActor var isAvailableToMarkUnread: Bool {
guard let markUnreadWindow = account?.behaviors.compactMap( { behavior -> Int? in
switch behavior {
case .disallowMarkAsUnreadAfterPeriod(let days):
@@ -117,11 +117,11 @@ extension Article {
}
}
func iconImage() -> IconImage? {
@MainActor func iconImage() -> IconImage? {
return IconImageCache.shared.imageForArticle(self)
}
func iconImageUrl(feed: Feed) -> URL? {
@MainActor func iconImageUrl(feed: Feed) -> URL? {
if let image = iconImage() {
let fm = FileManager.default
var path = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0]
@@ -138,7 +138,7 @@ extension Article {
}
}
func byline() -> String {
@MainActor func byline() -> String {
guard let authors = authors ?? feed?.authors, !authors.isEmpty else {
return ""
}
@@ -199,7 +199,7 @@ struct ArticlePathKey {
extension Article {
public var pathUserInfo: [AnyHashable : Any] {
@MainActor public var pathUserInfo: [AnyHashable : Any] {
return [
ArticlePathKey.accountID: accountID,
ArticlePathKey.accountName: account?.nameForDisplay ?? "",
@@ -214,7 +214,7 @@ extension Article {
extension Article: SortableArticle {
var sortableName: String {
@MainActor var sortableName: String {
return feed?.name ?? ""
}

View File

@@ -27,7 +27,7 @@ extension Account: SmallIconProvider {
extension Feed: SmallIconProvider {
var smallIcon: IconImage? {
@MainActor var smallIcon: IconImage? {
if let iconImage = appDelegate.faviconDownloader.favicon(for: self) {
return iconImage
}

View File

@@ -10,7 +10,7 @@ import Foundation
import RSCore
import Account
final class FaviconGenerator {
@MainActor final class FaviconGenerator {
private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage

View File

@@ -10,7 +10,7 @@ import Foundation
import Account
import Articles
final class IconImageCache {
@MainActor final class IconImageCache {
static let shared = IconImageCache()

View File

@@ -10,7 +10,7 @@ import Foundation
import Account
import RSCore
struct DefaultFeedsImporter {
@MainActor struct DefaultFeedsImporter {
static func importDefaultFeeds(account: Account) {
let defaultFeedsURL = Bundle.main.url(forResource: "DefaultFeeds", withExtension: "opml")!

View File

@@ -14,9 +14,9 @@ import Account
final class ExtensionContainersFile {
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionContainersFile")
private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionContainersFile")
private static var filePath: String = {
private static let filePath: String = {
let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
return containerURL!.appendingPathComponent("extension_containers.plist").path

View File

@@ -10,8 +10,8 @@ import Foundation
import os.log
import Account
final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Sendable {
private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionFeedAddRequestFile")
private static let filePath: String = {
@@ -30,7 +30,7 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
return operationQueue
}
override init() {
@MainActor override init() {
operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
@@ -41,14 +41,16 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
}
func presentedItemDidChange() {
DispatchQueue.main.async {
Task { @MainActor in
self.process()
}
}
func resume() {
NSFileCoordinator.addFilePresenter(self)
process()
Task { @MainActor in
process()
}
}
func suspend() {
@@ -95,8 +97,8 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
private extension ExtensionFeedAddRequestFile {
func process() {
@MainActor func process() {
let decoder = PropertyListDecoder()
let encoder = PropertyListEncoder()
encoder.outputFormat = .binary
@@ -130,7 +132,7 @@ private extension ExtensionFeedAddRequestFile {
requests?.forEach { processRequest($0) }
}
func processRequest(_ request: ExtensionFeedAddRequest) {
@MainActor func processRequest(_ request: ExtensionFeedAddRequest) {
var destinationAccountID: String? = nil
switch request.destinationContainerID {
case .account(let accountID):

View File

@@ -62,7 +62,7 @@ final class SmartFeed: PseudoFeed {
}
}
@objc func fetchUnreadCounts() {
@objc @MainActor func fetchUnreadCounts() {
let activeAccounts = AccountManager.shared.activeAccounts
// Remove any accounts that are no longer active or have been deleted
@@ -113,15 +113,18 @@ private extension SmartFeed {
func fetchUnreadCount(for account: Account) {
delegate.fetchUnreadCount(for: account) { singleUnreadCountResult in
guard let accountUnreadCount = try? singleUnreadCountResult.get() else {
return
MainActor.assumeIsolated {
guard let accountUnreadCount = try? singleUnreadCountResult.get() else {
return
}
self.unreadCounts[account.accountID] = accountUnreadCount
self.updateUnreadCount()
}
self.unreadCounts[account.accountID] = accountUnreadCount
self.updateUnreadCount()
}
}
func updateUnreadCount() {
@MainActor func updateUnreadCount() {
unreadCount = AccountManager.shared.activeAccounts.reduce(0) { (result, account) -> Int in
if let oneUnreadCount = unreadCounts[account.accountID] {
return result + oneUnreadCount

View File

@@ -51,13 +51,13 @@ final class UnreadFeed: PseudoFeed {
}
#endif
init() {
@MainActor init() {
self.unreadCount = appDelegate.unreadCount
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: appDelegate)
}
@objc func unreadCountDidChange(_ note: Notification) {
@objc @MainActor func unreadCountDidChange(_ note: Notification) {
assert(note.object is AppDelegate)
unreadCount = appDelegate.unreadCount

View File

@@ -67,7 +67,7 @@ extension Array where Element == Article {
return false
}
func anyArticleIsReadAndCanMarkUnread() -> Bool {
@MainActor func anyArticleIsReadAndCanMarkUnread() -> Bool {
return anyArticlePassesTest { $0.status.read && $0.isAvailableToMarkUnread }
}
@@ -95,7 +95,7 @@ extension Array where Element == Article {
var i = 0
for article in self {
let otherArticle = otherArticles[i]
if article.account != otherArticle.account || article.articleID != otherArticle.articleID {
if article.accountID != otherArticle.accountID || article.articleID != otherArticle.articleID {
return false
}
i += 1

View File

@@ -9,7 +9,7 @@
import Foundation
import Account
class AccountRefreshTimer {
@MainActor final class AccountRefreshTimer {
var shuttingDown = false
@@ -64,7 +64,7 @@ class AccountRefreshTimer {
}
@objc func timedRefresh(_ sender: Timer?) {
@objc @MainActor func timedRefresh(_ sender: Timer?) {
guard !shuttingDown else {
return

View File

@@ -9,8 +9,8 @@
import Foundation
import Account
class ArticleStatusSyncTimer {
@MainActor final class ArticleStatusSyncTimer {
private static let intervalSeconds = Double(120)
var shuttingDown = false

View File

@@ -24,7 +24,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate {
filterExceptions = Set<SidebarItemIdentifier>()
}
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
@MainActor func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
if node.isRoot {
return childNodesForRootNode(node)
}
@@ -41,7 +41,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate {
private extension FeedTreeControllerDelegate {
func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
@MainActor func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
var topLevelNodes = [Node]()
let smartFeedsNode = rootNode.existingOrNewChildNode(with: SmartFeedsController.shared)
@@ -132,7 +132,7 @@ private extension FeedTreeControllerDelegate {
return node
}
func sortedAccountNodes(_ parent: Node) -> [Node] {
@MainActor func sortedAccountNodes(_ parent: Node) -> [Node] {
let nodes = AccountManager.shared.sortedActiveAccounts.compactMap { (account) -> Node? in
let accountNode = parent.existingOrNewChildNode(with: account)
accountNode.canHaveChildNodes = true

View File

@@ -14,7 +14,7 @@ import Account
final class FolderTreeControllerDelegate: TreeControllerDelegate {
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
@MainActor func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
return node.isRoot ? childNodesForRootNode(node) : childNodes(node)
}
@@ -22,8 +22,8 @@ final class FolderTreeControllerDelegate: TreeControllerDelegate {
private extension FolderTreeControllerDelegate {
func childNodesForRootNode(_ node: Node) -> [Node]? {
@MainActor func childNodesForRootNode(_ node: Node) -> [Node]? {
let accountNodes: [Node] = AccountManager.shared.sortedActiveAccounts.map { account in
let accountNode = Node(representedObject: account, parent: node)
accountNode.canHaveChildNodes = true

View File

@@ -25,9 +25,11 @@ final class UserNotificationManager: NSObject {
return
}
for article in articles {
if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
sendNotification(feed: feed, article: article)
Task { @MainActor in
for article in articles {
if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
sendNotification(feed: feed, article: article)
}
}
}
}
@@ -53,7 +55,7 @@ final class UserNotificationManager: NSObject {
private extension UserNotificationManager {
func sendNotification(feed: Feed, article: Article) {
@MainActor func sendNotification(feed: Feed, article: Article) {
let content = UNMutableNotificationContent()
content.title = feed.nameForDisplay
@@ -79,7 +81,7 @@ private extension UserNotificationManager {
/// - feed: `Feed`
/// - Returns: A `UNNotifcationAttachment` if an icon is available. Otherwise nil.
/// - Warning: In certain scenarios, this will return the `faviconTemplateImage`.
func thumbnailAttachment(for article: Article, feed: Feed) -> UNNotificationAttachment? {
@MainActor func thumbnailAttachment(for article: Article, feed: Feed) -> UNNotificationAttachment? {
if let imageURL = article.iconImageUrl(feed: feed) {
let thumbnail = try? UNNotificationAttachment(identifier: feed.feedID, url: imageURL, options: nil)
return thumbnail