mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Create and use BackgroundTaskManager.
This commit is contained in:
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import BackgroundTasks
|
||||
import os
|
||||
import RSCore
|
||||
import Account
|
||||
@@ -17,16 +16,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
private var backgroundTaskDispatchQueue = DispatchQueue.init(label: "BGTaskScheduler")
|
||||
|
||||
private var waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
private var isWaitingForSyncTasks = false
|
||||
|
||||
private var syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
private var isSyncArticleStatusRunning = false
|
||||
|
||||
private var coordinator: SceneCoordinator?
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
||||
|
||||
private var unreadCount = 0 {
|
||||
@@ -61,13 +51,14 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
|
||||
}
|
||||
|
||||
if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() {
|
||||
let localAccount = AccountManager.shared.defaultAccount
|
||||
DefaultFeedsImporter.importDefaultFeeds(account: localAccount)
|
||||
DefaultFeedsImporter.importDefaultFeeds(account: AccountManager.shared.defaultAccount)
|
||||
}
|
||||
|
||||
registerBackgroundTasks()
|
||||
BackgroundTaskManager.shared.delegate = self
|
||||
BackgroundTaskManager.shared.registerTasks()
|
||||
|
||||
CacheCleaner.purgeIfNecessary()
|
||||
initializeHomeScreenQuickActions()
|
||||
addHomeScreenQuickActions()
|
||||
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) { granted, _ in
|
||||
guard granted else { return }
|
||||
@@ -77,7 +68,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
|
||||
}
|
||||
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
|
||||
|
||||
_ = UserNotificationManager.shared
|
||||
_ = ExtensionContainersFile.shared
|
||||
_ = ExtensionFeedAddRequestFile.shared
|
||||
@@ -85,10 +76,10 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
|
||||
_ = ArticleStatusSyncTimer.shared
|
||||
_ = FaviconDownloader.shared
|
||||
_ = FeedIconDownloader.shared
|
||||
|
||||
#if DEBUG
|
||||
|
||||
#if DEBUG
|
||||
ArticleStatusSyncTimer.shared.update()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Create window.
|
||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||
@@ -119,14 +110,13 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
|
||||
rootSplitViewController.show(.primary)
|
||||
}
|
||||
|
||||
// Update unread count.
|
||||
self.unreadCount = AccountManager.shared.unreadCount
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
AccountManager.shared.resumeAllIfSuspended()
|
||||
AccountManager.shared.receiveRemoteNotification(userInfo: userInfo) {
|
||||
@@ -134,60 +124,17 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
|
||||
completionHandler(.newData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
ArticleStatusSyncTimer.shared.stop()
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
IconImageCache.shared.emptyCache()
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
prepareAccountsForForeground()
|
||||
coordinator?.resetFocus()
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
private func prepareAccountsForForeground() {
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
assert(Thread.isMainThread)
|
||||
assert(note.object is AccountManager)
|
||||
unreadCount = AccountManager.shared.unreadCount
|
||||
}
|
||||
AccountManager.shared.resumeAllIfSuspended()
|
||||
|
||||
@objc func accountRefreshDidFinish(_ note: Notification) {
|
||||
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)
|
||||
}
|
||||
|
||||
@objc func userDefaultsDidChange(_ note: Notification) {
|
||||
updateUserInterfaceStyle()
|
||||
}
|
||||
|
||||
// MARK: - API
|
||||
|
||||
func manualRefresh(errorHandler: @escaping ErrorHandlerBlock) {
|
||||
assert(Thread.isMainThread)
|
||||
coordinator?.cleanUp(conditional: true)
|
||||
AccountManager.shared.refreshAll(errorHandler: errorHandler)
|
||||
}
|
||||
|
||||
func prepareAccountsForBackground() {
|
||||
ExtensionFeedAddRequestFile.shared.suspend()
|
||||
ArticleStatusSyncTimer.shared.invalidate()
|
||||
scheduleBackgroundFeedRefresh()
|
||||
syncArticleStatus()
|
||||
WidgetDataEncoder.shared.encode()
|
||||
waitForSyncTasksToFinish()
|
||||
}
|
||||
|
||||
func prepareAccountsForForeground() {
|
||||
ExtensionFeedAddRequestFile.shared.resume()
|
||||
ArticleStatusSyncTimer.shared.update()
|
||||
|
||||
@@ -202,6 +149,74 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
|
||||
}
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
IconImageCache.shared.emptyCache()
|
||||
ArticleStringFormatter.emptyCaches()
|
||||
prepareAccountsForBackground()
|
||||
}
|
||||
|
||||
private func prepareAccountsForBackground() {
|
||||
ExtensionFeedAddRequestFile.shared.suspend()
|
||||
ArticleStatusSyncTimer.shared.invalidate()
|
||||
BackgroundTaskManager.shared.scheduleBackgroundFeedRefresh()
|
||||
BackgroundTaskManager.shared.syncArticleStatus()
|
||||
WidgetDataEncoder.shared.encode()
|
||||
BackgroundTaskManager.shared.waitForSyncTasksToFinish()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
ArticleStatusSyncTimer.shared.stop()
|
||||
}
|
||||
|
||||
private func suspendApplication() {
|
||||
guard UIApplication.shared.applicationState == .background else { return }
|
||||
|
||||
AccountManager.shared.suspendNetworkAll()
|
||||
AccountManager.shared.suspendDatabaseAll()
|
||||
ArticleThemeDownloader.shared.cleanUp()
|
||||
|
||||
CoalescingQueue.standard.performCallsImmediately()
|
||||
coordinator?.suspend()
|
||||
logger.info("Application processing suspended.")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
extension AppDelegate {
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
assert(Thread.isMainThread)
|
||||
assert(note.object is AccountManager)
|
||||
unreadCount = AccountManager.shared.unreadCount
|
||||
}
|
||||
|
||||
@objc func accountRefreshDidFinish(_ note: Notification) {
|
||||
AppDefaults.lastRefresh = Date()
|
||||
}
|
||||
|
||||
@objc func userDidTriggerManualRefresh(_ note: Notification) {
|
||||
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
guard let errorHandler = note.userInfo?[UserInfoKey.errorHandler] as? ErrorHandlerBlock else {
|
||||
assertionFailure("Expected errorHandler in .userDidTriggerManualRefresh userInfo")
|
||||
return
|
||||
}
|
||||
|
||||
coordinator?.cleanUp(conditional: true)
|
||||
AccountManager.shared.refreshAll(errorHandler: errorHandler)
|
||||
}
|
||||
|
||||
@objc func userDefaultsDidChange(_ note: Notification) {
|
||||
updateUserInterfaceStyle()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UNUserNotificationCenterDelegate
|
||||
|
||||
extension AppDelegate {
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
completionHandler([.list, .banner, .badge, .sound])
|
||||
}
|
||||
@@ -225,7 +240,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - App Initialization
|
||||
// MARK: - Home Screen Quick Actions
|
||||
|
||||
private extension AppDelegate {
|
||||
|
||||
@@ -235,7 +250,7 @@ private extension AppDelegate {
|
||||
case addFeed = "com.ranchero.NetNewsWire.ShowAdd"
|
||||
}
|
||||
|
||||
private func initializeHomeScreenQuickActions() {
|
||||
private func addHomeScreenQuickActions() {
|
||||
let unreadTitle = NSLocalizedString("First Unread", comment: "First Unread")
|
||||
let unreadIcon = UIApplicationShortcutIcon(systemImageName: "chevron.down.circle")
|
||||
let unreadItem = UIApplicationShortcutItem(type: ShortcutItemType.firstUnread.rawValue, localizedTitle: unreadTitle, localizedSubtitle: nil, icon: unreadIcon, userInfo: nil)
|
||||
@@ -271,151 +286,12 @@ private extension AppDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Go To Background
|
||||
// MARK: - BackgroundTaskManagerDelegate
|
||||
|
||||
private extension AppDelegate {
|
||||
extension AppDelegate: BackgroundTaskManagerDelegate {
|
||||
|
||||
func waitForSyncTasksToFinish() {
|
||||
guard !isWaitingForSyncTasks && UIApplication.shared.applicationState == .background else { return }
|
||||
|
||||
isWaitingForSyncTasks = true
|
||||
|
||||
self.waitBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.completeProcessing(true)
|
||||
logger.info("Accounts wait for progress terminated for running too long.")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.waitToComplete { [weak self] suspend in
|
||||
self?.completeProcessing(suspend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitToComplete(completion: @escaping (Bool) -> Void) {
|
||||
guard UIApplication.shared.applicationState == .background else {
|
||||
logger.info("App came back to foreground, no longer waiting.")
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning || WidgetDataEncoder.shared.isRunning {
|
||||
logger.info("Waiting for sync to finish…")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
||||
self?.waitToComplete(completion: completion)
|
||||
}
|
||||
} else {
|
||||
logger.info("Refresh progress complete.")
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
|
||||
func completeProcessing(_ suspend: Bool) {
|
||||
if suspend {
|
||||
suspendApplication()
|
||||
}
|
||||
UIApplication.shared.endBackgroundTask(self.waitBackgroundUpdateTask)
|
||||
self.waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
isWaitingForSyncTasks = false
|
||||
}
|
||||
|
||||
func syncArticleStatus() {
|
||||
guard !isSyncArticleStatusRunning else { return }
|
||||
|
||||
isSyncArticleStatusRunning = true
|
||||
|
||||
let completeProcessing = { [unowned self] in
|
||||
self.isSyncArticleStatusRunning = false
|
||||
UIApplication.shared.endBackgroundTask(self.syncBackgroundUpdateTask)
|
||||
self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
}
|
||||
|
||||
self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask {
|
||||
completeProcessing()
|
||||
self.logger.info("Accounts sync processing terminated for running too long.")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
AccountManager.shared.syncArticleStatusAll {
|
||||
completeProcessing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func suspendApplication() {
|
||||
guard UIApplication.shared.applicationState == .background else { return }
|
||||
|
||||
AccountManager.shared.suspendNetworkAll()
|
||||
AccountManager.shared.suspendDatabaseAll()
|
||||
ArticleThemeDownloader.shared.cleanUp()
|
||||
|
||||
CoalescingQueue.standard.performCallsImmediately()
|
||||
coordinator?.suspend()
|
||||
logger.info("Application processing suspended.")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Background Tasks
|
||||
|
||||
private extension AppDelegate {
|
||||
|
||||
static let refreshTaskIdentifier = "com.ranchero.NetNewsWire.FeedRefresh"
|
||||
|
||||
/// Register all background tasks.
|
||||
func registerBackgroundTasks() {
|
||||
// Register background feed refresh.
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: Self.refreshTaskIdentifier, using: nil) { (task) in
|
||||
self.performBackgroundFeedRefresh(with: task as! BGAppRefreshTask)
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules a background app refresh based on `AppDefaults.refreshInterval`.
|
||||
func scheduleBackgroundFeedRefresh() {
|
||||
let request = BGAppRefreshTaskRequest(identifier: Self.refreshTaskIdentifier)
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
||||
|
||||
// We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the
|
||||
// task scheduler can hang indefinitely.
|
||||
backgroundTaskDispatchQueue.async {
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
} catch {
|
||||
self.logger.error("Could not schedule app refresh: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs background feed refresh.
|
||||
/// - Parameter task: `BGAppRefreshTask`
|
||||
/// - Warning: As of Xcode 11 beta 2, when triggered from the debugger this doesn't work.
|
||||
func performBackgroundFeedRefresh(with task: BGAppRefreshTask) {
|
||||
|
||||
scheduleBackgroundFeedRefresh() // schedule next refresh
|
||||
|
||||
logger.info("Woken to perform account refresh.")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
}
|
||||
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in
|
||||
if !AccountManager.shared.isSuspended {
|
||||
self.suspendApplication()
|
||||
logger.info("Account refresh operation completed.")
|
||||
task.setTaskCompleted(success: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set expiration handler
|
||||
task.expirationHandler = { [weak task] in
|
||||
self.logger.info("Accounts refresh processing terminated for running too long.")
|
||||
DispatchQueue.main.async {
|
||||
self.suspendApplication()
|
||||
task?.setTaskCompleted(success: false)
|
||||
}
|
||||
}
|
||||
func backgroundTaskManagerApplicationShouldSuspend(_: BackgroundTaskManager) {
|
||||
suspendApplication()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,7 +317,7 @@ private extension AppDelegate {
|
||||
return
|
||||
}
|
||||
account!.markArticles(article!, statusKey: .read, flag: true) { _ in }
|
||||
self.prepareAccountsForBackground()
|
||||
prepareAccountsForBackground()
|
||||
account!.syncArticleStatus(completion: { [weak self] _ in
|
||||
if !AccountManager.shared.isSuspended {
|
||||
self?.prepareAccountsForBackground()
|
||||
|
||||
169
iOS/BackgroundTaskManager.swift
Normal file
169
iOS/BackgroundTaskManager.swift
Normal file
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// BackgroundTaskManager.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Brent Simmons on 2/1/25.
|
||||
// Copyright © 2025 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import BackgroundTasks
|
||||
import os
|
||||
import Account
|
||||
|
||||
protocol BackgroundTaskManagerDelegate: AnyObject {
|
||||
/// Called when application should suspend networking, database, and other processing.
|
||||
func backgroundTaskManagerApplicationShouldSuspend(_: BackgroundTaskManager)
|
||||
}
|
||||
|
||||
/// Registers and runs background tasks using the iOS BackgroundTasks API.
|
||||
final class BackgroundTaskManager {
|
||||
|
||||
static let shared = BackgroundTaskManager()
|
||||
|
||||
weak var delegate: BackgroundTaskManagerDelegate?
|
||||
|
||||
private var backgroundTaskDispatchQueue = DispatchQueue.init(label: "BGTaskScheduler")
|
||||
|
||||
private var waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
private var isWaitingForSyncTasks = false
|
||||
|
||||
private var syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
private var isSyncArticleStatusRunning = false
|
||||
|
||||
static let refreshTaskIdentifier = "com.ranchero.NetNewsWire.FeedRefresh"
|
||||
|
||||
private static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "BackgroundTasks")
|
||||
|
||||
/// Register background feed refresh.
|
||||
func registerTasks() {
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: Self.refreshTaskIdentifier, using: nil) { task in
|
||||
self.performBackgroundFeedRefresh(with: task as! BGAppRefreshTask)
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules a background app refresh based on `AppDefaults.refreshInterval`.
|
||||
func scheduleBackgroundFeedRefresh() {
|
||||
let request = BGAppRefreshTaskRequest(identifier: Self.refreshTaskIdentifier)
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
||||
|
||||
// We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the
|
||||
// task scheduler can hang indefinitely.
|
||||
backgroundTaskDispatchQueue.async {
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
} catch {
|
||||
Self.logger.error("Could not schedule app refresh: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitForSyncTasksToFinish() {
|
||||
guard !isWaitingForSyncTasks && UIApplication.shared.applicationState == .background else { return }
|
||||
|
||||
isWaitingForSyncTasks = true
|
||||
|
||||
waitBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask {
|
||||
self.completeProcessing(true)
|
||||
Self.logger.info("Accounts wait for progress terminated for running too long.")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.waitToComplete { suspend in
|
||||
self.completeProcessing(suspend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func syncArticleStatus() {
|
||||
guard !isSyncArticleStatusRunning else { return }
|
||||
|
||||
isSyncArticleStatusRunning = true
|
||||
|
||||
let completeProcessing = { [unowned self] in
|
||||
self.isSyncArticleStatusRunning = false
|
||||
UIApplication.shared.endBackgroundTask(self.syncBackgroundUpdateTask)
|
||||
self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
}
|
||||
|
||||
self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask {
|
||||
completeProcessing()
|
||||
Self.logger.info("Accounts sync processing terminated for running too long.")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
AccountManager.shared.syncArticleStatusAll {
|
||||
completeProcessing()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension BackgroundTaskManager {
|
||||
|
||||
/// Performs background feed refresh.
|
||||
/// - Parameter task: `BGAppRefreshTask`
|
||||
/// - Warning: As of Xcode 11 beta 2, when triggered from the debugger this doesn't work.
|
||||
func performBackgroundFeedRefresh(with task: BGAppRefreshTask) {
|
||||
|
||||
scheduleBackgroundFeedRefresh() // schedule next refresh
|
||||
|
||||
Self.logger.info("Woken to perform account refresh.")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
}
|
||||
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) {
|
||||
if !AccountManager.shared.isSuspended {
|
||||
self.suspendApplication()
|
||||
Self.logger.info("Account refresh operation completed.")
|
||||
task.setTaskCompleted(success: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set expiration handler
|
||||
task.expirationHandler = { [weak task] in
|
||||
Self.logger.info("Accounts refresh processing terminated for running too long.")
|
||||
DispatchQueue.main.async {
|
||||
self.suspendApplication()
|
||||
task?.setTaskCompleted(success: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitToComplete(completion: @escaping (Bool) -> Void) {
|
||||
guard UIApplication.shared.applicationState == .background else {
|
||||
Self.logger.info("App came back to foreground, no longer waiting.")
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning || WidgetDataEncoder.shared.isRunning {
|
||||
Self.logger.info("Waiting for sync to finish…")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
self.waitToComplete(completion: completion)
|
||||
}
|
||||
} else {
|
||||
Self.logger.info("Refresh progress complete.")
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
|
||||
func completeProcessing(_ suspend: Bool) {
|
||||
if suspend {
|
||||
suspendApplication()
|
||||
}
|
||||
UIApplication.shared.endBackgroundTask(self.waitBackgroundUpdateTask)
|
||||
waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
isWaitingForSyncTasks = false
|
||||
}
|
||||
|
||||
func suspendApplication() {
|
||||
assert(delegate != nil)
|
||||
assert(Thread.isMainThread)
|
||||
delegate?.backgroundTaskManagerApplicationShouldSuspend(self)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user