Remove most uses of AppDelegate.appDelegate on iOS.

This commit is contained in:
Brent Simmons 2025-02-01 13:28:49 -08:00
parent 2fed6d3cb3
commit 900cbabe71
10 changed files with 65 additions and 24 deletions

View File

@ -7,6 +7,7 @@
//
import Foundation
import os
import RSCore
import RSWeb
import Articles
@ -90,6 +91,7 @@ public final class AccountManager: UnreadCountProvider {
}
public let combinedRefreshProgress = CombinedRefreshProgress()
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AccountManager")
public init(accountsFolder: String) {
self.accountsFolder = accountsFolder
@ -221,6 +223,13 @@ public final class AccountManager: UnreadCountProvider {
}
}
public func resumeAllIfSuspended() {
if isSuspended {
resumeAll()
logger.info("Account processing resumed.")
}
}
public func resumeAll() {
isSuspended = false
for account in accounts {

View File

@ -15,7 +15,7 @@ struct UserInfoKey {
static let articlePath = "articlePath"
static let feedIdentifier = "feedIdentifier"
static let draggedFeed = "draggedFeed" // DraggedFeed struct
static let errorHandler = "errorHandler" // ErrorHandlerBlock
static let windowState = "windowState"
static let windowFullScreenState = "windowFullScreenState"
static let containerExpandedWindowState = "containerExpandedWindowState"

View File

@ -70,6 +70,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountRefreshDidFinish(_:)), name: .AccountRefreshDidFinish, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidTriggerManualRefresh(_:)), name: .userDidTriggerManualRefresh, object: nil)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
@ -122,7 +123,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
DispatchQueue.main.async {
self.resumeDatabaseProcessingIfNecessary()
AccountManager.shared.resumeAllIfSuspended()
AccountManager.shared.receiveRemoteNotification(userInfo: userInfo) {
self.suspendApplication()
completionHandler(.newData)
@ -150,22 +151,27 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
AppDefaults.lastRefresh = Date()
}
@objc func userDidTriggerManualRefresh(_ note: Notification) {
guard let errorHandler = note.userInfo?[UserInfoKey.errorHandler] as? ErrorHandlerBlock else {
assertionFailure("Expected errorHandler in .userDidTriggerManualRefresh userInfo")
return
}
manualRefresh(errorHandler: errorHandler)
}
// MARK: - API
func manualRefresh(errorHandler: @escaping (Error) -> Void) {
func manualRefresh(errorHandler: @escaping ErrorHandlerBlock) {
assert(Thread.isMainThread)
UIApplication.shared.connectedScenes.compactMap( { $0.delegate as? SceneDelegate }).forEach {
$0.cleanUp(conditional: true)
}
AccountManager.shared.refreshAll(errorHandler: errorHandler)
}
func resumeDatabaseProcessingIfNecessary() {
if AccountManager.shared.isSuspended {
AccountManager.shared.resumeAll()
logger.info("Application processing resumed.")
}
}
func prepareAccountsForBackground() {
extensionFeedAddRequestFile.suspend()
syncTimer?.invalidate()
@ -408,7 +414,7 @@ private extension AppDelegate {
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
return
}
resumeDatabaseProcessingIfNecessary()
AccountManager.shared.resumeAllIfSuspended()
let account = AccountManager.shared.existingAccount(with: accountID)
guard account != nil else {
os_log(.debug, "No account found from notification.")
@ -435,7 +441,7 @@ private extension AppDelegate {
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
return
}
resumeDatabaseProcessingIfNecessary()
AccountManager.shared.resumeAllIfSuspended()
let account = AccountManager.shared.existingAccount(with: accountID)
guard account != nil else {
os_log(.debug, "No account found from notification.")

View File

@ -10,11 +10,13 @@ import UIKit
import RSCore
import os.log
typealias ErrorHandlerBlock = (Error) -> Void
struct ErrorHandler {
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
public static func present(_ viewController: UIViewController) -> (Error) -> Void {
public static func present(_ viewController: UIViewController) -> ErrorHandlerBlock {
return { [weak viewController] error in
if UIApplication.shared.applicationState == .active {
viewController?.presentError(error)
@ -27,5 +29,4 @@ struct ErrorHandler {
public static func log(_ error: Error) {
os_log(.error, log: self.log, "%@", error.localizedDescription)
}
}

View File

@ -471,7 +471,7 @@ final class MainFeedViewController: UITableViewController, UndoableCommandRunner
// This is a hack to make sure that an error dialog doesn't interfere with dismissing the refreshControl.
// If the error dialog appears too closely to the call to endRefreshing, then the refreshControl never disappears.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
appDelegate.manualRefresh(errorHandler: ErrorHandler.present(self))
ManualRefreshNotification.post(errorHandler: ErrorHandler.present(self), sender: self)
}
}

View File

@ -184,7 +184,7 @@ final class TimelineViewController: UITableViewController, UndoableCommandRunner
// This is a hack to make sure that an error dialog doesn't interfere with dismissing the refreshControl.
// If the error dialog appears too closely to the call to endRefreshing, then the refreshControl never disappears.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
appDelegate.manualRefresh(errorHandler: ErrorHandler.present(self))
ManualRefreshNotification.post(errorHandler: ErrorHandler.present(self), sender: self)
}
}

View File

@ -0,0 +1,25 @@
//
// ManualRefreshNotification.swift
// NetNewsWire
//
// Created by Brent Simmons on 2/1/25.
// Copyright © 2025 Ranchero Software. All rights reserved.
//
import Foundation
extension Notification.Name {
static let userDidTriggerManualRefresh = Notification.Name("userDidTriggerManualRefresh")
}
// See UserInfoKey.errorHandler for the required ErrorHandler
struct ManualRefreshNotification {
static func post(errorHandler: @escaping ErrorHandlerBlock, sender: Any?) {
Task { @MainActor in
let userInfo = [UserInfoKey.errorHandler: errorHandler]
NotificationCenter.default.post(name: .userDidTriggerManualRefresh, object: sender, userInfo: userInfo)
}
}
}

View File

@ -116,7 +116,7 @@ final class RootSplitViewController: UISplitViewController {
}
@objc func refresh(_ sender: Any?) {
appDelegate.manualRefresh(errorHandler: ErrorHandler.present(self))
ManualRefreshNotification.post(errorHandler: ErrorHandler.present(self), sender: self)
}
@objc func goToToday(_ sender: Any?) {

View File

@ -267,7 +267,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner {
}
var isAnyUnreadAvailable: Bool {
return appDelegate.unreadCount > 0
return AccountManager.shared.unreadCount > 0
}
var timelineUnreadCount: Int = 0
@ -918,7 +918,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner {
// This should never happen, but I don't want to risk throwing us
// into an infinite loop searching for an unread that isn't there.
if appDelegate.unreadCount < 1 {
if AccountManager.shared.unreadCount < 1 {
return
}
@ -939,7 +939,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner {
// This should never happen, but I don't want to risk throwing us
// into an infinite loop searching for an unread that isn't there.
if appDelegate.unreadCount < 1 {
if AccountManager.shared.unreadCount < 1 {
return
}

View File

@ -65,13 +65,13 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
appDelegate.resumeDatabaseProcessingIfNecessary()
AccountManager.shared.resumeAllIfSuspended()
handleShortcutItem(shortcutItem)
completionHandler(true)
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
appDelegate.resumeDatabaseProcessingIfNecessary()
AccountManager.shared.resumeAllIfSuspended()
coordinator.handle(userActivity)
}
@ -81,7 +81,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
func sceneWillEnterForeground(_ scene: UIScene) {
appDelegate.resumeDatabaseProcessingIfNecessary()
AccountManager.shared.resumeAllIfSuspended()
appDelegate.prepareAccountsForForeground()
coordinator.resetFocus()
}
@ -93,7 +93,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// API
func handle(_ response: UNNotificationResponse) {
appDelegate.resumeDatabaseProcessingIfNecessary()
AccountManager.shared.resumeAllIfSuspended()
coordinator.handle(response)
}