mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 11:58:32 +00:00
Introduce an AppMediator which will:
- combine multiple services to handle app wide requests - remove direct UIApplication access from everywhere - fix #2703 by combining UIApplication.applicationState with the WindowManager's main window appearance - leave the door open for more app control in the future (especiall for the mac)
This commit is contained in:
committed by
Stefan Ceriu
parent
bdacea10a2
commit
9a172df7fd
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol ApplicationProtocol {
|
||||
func beginBackgroundTask(withName taskName: String?, expirationHandler handler: (() -> Void)?) -> UIBackgroundTaskIdentifier
|
||||
|
||||
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
|
||||
|
||||
func open(_ url: URL)
|
||||
|
||||
func openAppSettings()
|
||||
|
||||
var backgroundTimeRemaining: TimeInterval { get }
|
||||
|
||||
var applicationState: UIApplication.State { get }
|
||||
}
|
||||
|
||||
extension UIApplication: ApplicationProtocol {
|
||||
func open(_ url: URL) {
|
||||
open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
|
||||
func openAppSettings() {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else {
|
||||
return
|
||||
}
|
||||
open(url)
|
||||
}
|
||||
}
|
||||
+5
-15
@@ -17,7 +17,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// UIKitBackgroundTask is a concrete implementation of BackgroundTaskProtocol using UIApplication background task.
|
||||
class UIKitBackgroundTask: BackgroundTaskProtocol {
|
||||
let name: String
|
||||
var isRunning: Bool {
|
||||
@@ -30,28 +29,21 @@ class UIKitBackgroundTask: BackgroundTaskProtocol {
|
||||
Date().timeIntervalSince(startDate) * 1000
|
||||
}
|
||||
|
||||
private let application: ApplicationProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private var identifier: UIBackgroundTaskIdentifier = .invalid
|
||||
private var useCounter = 0
|
||||
private let startDate = Date()
|
||||
|
||||
/// Initializes and starts a new background task
|
||||
/// - Parameters:
|
||||
/// - name: name
|
||||
/// - isReusable: flag indicating the task is reusable
|
||||
/// - application: application instance
|
||||
/// - expirationHandler: expiration handler
|
||||
init?(name: String,
|
||||
isReusable: Bool,
|
||||
application: ApplicationProtocol,
|
||||
appMediator: AppMediatorProtocol,
|
||||
expirationHandler: BackgroundTaskExpirationHandler?) {
|
||||
self.name = name
|
||||
self.isReusable = isReusable
|
||||
self.application = application
|
||||
self.appMediator = appMediator
|
||||
self.expirationHandler = expirationHandler
|
||||
|
||||
// attempt to start
|
||||
identifier = application.beginBackgroundTask(withName: name) { [weak self] in
|
||||
identifier = appMediator.beginBackgroundTask(withName: name) { [weak self] in
|
||||
guard let self else { return }
|
||||
self.stop()
|
||||
self.expirationHandler?(self)
|
||||
@@ -59,13 +51,11 @@ class UIKitBackgroundTask: BackgroundTaskProtocol {
|
||||
|
||||
if identifier == .invalid {
|
||||
MXLog.error("Do not start background task: \(name), as OS declined")
|
||||
// call expiration handler immediately
|
||||
expirationHandler?(self)
|
||||
return nil
|
||||
}
|
||||
|
||||
if isReusable {
|
||||
// creation itself is a use
|
||||
reuse()
|
||||
}
|
||||
|
||||
@@ -94,7 +84,7 @@ class UIKitBackgroundTask: BackgroundTaskProtocol {
|
||||
if identifier != .invalid {
|
||||
MXLog.verbose("End background task #\(identifier.rawValue) - \(name) after \(readableElapsedTime)")
|
||||
|
||||
application.endBackgroundTask(identifier)
|
||||
appMediator.endBackgroundTask(identifier)
|
||||
identifier = .invalid
|
||||
}
|
||||
}
|
||||
+12
-47
@@ -19,28 +19,17 @@ import UIKit
|
||||
|
||||
/// /// UIKitBackgroundTaskService is a concrete implementation of BackgroundTaskServiceProtocol using a given `ApplicationProtocol` instance.
|
||||
class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
private let applicationBlock: () -> ApplicationProtocol?
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private var reusableTasks = NSMapTable<NSString, UIKitBackgroundTask>(keyOptions: .strongMemory, valueOptions: .weakMemory)
|
||||
|
||||
private var application: ApplicationProtocol? {
|
||||
applicationBlock()
|
||||
}
|
||||
|
||||
/// Initializer
|
||||
/// - Parameter applicationBlock: block returning the application instance to use. Defaults to a block returning `UIApplication.extensionSafeShared`.
|
||||
init(withApplicationBlock applicationBlock: @escaping () -> ApplicationProtocol? = { UIApplication.extensionSafeShared }) {
|
||||
self.applicationBlock = applicationBlock
|
||||
|
||||
init(appMediator: AppMediatorProtocol) {
|
||||
self.appMediator = appMediator
|
||||
}
|
||||
|
||||
func startBackgroundTask(withName name: String,
|
||||
isReusable: Bool,
|
||||
expirationHandler: (() -> Void)?) -> BackgroundTaskProtocol? {
|
||||
guard let application else {
|
||||
MXLog.error("Do not start background task: \(name). Application is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
if avoidStartingNewTasks(for: application) {
|
||||
if shouldAvoidStartingNewTasks() {
|
||||
MXLog.error("Do not start background task: \(name), as not enough time exists")
|
||||
// call expiration handler immediately
|
||||
expirationHandler?()
|
||||
@@ -56,7 +45,7 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
} else {
|
||||
if let newTask = UIKitBackgroundTask(name: name,
|
||||
isReusable: isReusable,
|
||||
application: application,
|
||||
appMediator: appMediator,
|
||||
expirationHandler: { [weak self] task in
|
||||
guard let self else { return }
|
||||
self.reusableTasks.removeObject(forKey: task.name as NSString)
|
||||
@@ -70,7 +59,7 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
} else {
|
||||
if let newTask = UIKitBackgroundTask(name: name,
|
||||
isReusable: isReusable,
|
||||
application: application,
|
||||
appMediator: appMediator,
|
||||
expirationHandler: { _ in
|
||||
expirationHandler?()
|
||||
}) {
|
||||
@@ -79,8 +68,8 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
let appState = application.applicationState
|
||||
let remainingTime = readableBackgroundTimeRemaining(application.backgroundTimeRemaining)
|
||||
let appState = appMediator.appState
|
||||
let remainingTime = readableBackgroundTimeRemaining(appMediator.backgroundTimeRemaining)
|
||||
|
||||
MXLog.verbose("Background task \(name) \(created ? "started" : "reused") with app state: \(appState) and estimated background time remaining: \(remainingTime)")
|
||||
|
||||
@@ -95,9 +84,9 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func avoidStartingNewTasks(for application: ApplicationProtocol) -> Bool {
|
||||
if application.applicationState == .background,
|
||||
application.backgroundTimeRemaining < .backgroundTimeRemainingThresholdToStartTasks {
|
||||
private func shouldAvoidStartingNewTasks() -> Bool {
|
||||
if appMediator.appState == .background,
|
||||
appMediator.backgroundTimeRemaining < .backgroundTimeRemainingThresholdToStartTasks {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -107,27 +96,3 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
private extension TimeInterval {
|
||||
static let backgroundTimeRemainingThresholdToStartTasks: TimeInterval = 5
|
||||
}
|
||||
|
||||
private extension UIApplication {
|
||||
/// Application instance extension-safe. Will be `nil` on app extensions.
|
||||
static var extensionSafeShared: UIApplication? {
|
||||
let selector = NSSelectorFromString("sharedApplication")
|
||||
guard responds(to: selector) else { return nil }
|
||||
return perform(selector).takeUnretainedValue() as? UIApplication
|
||||
}
|
||||
}
|
||||
|
||||
extension UIApplication.State: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .active:
|
||||
return "active"
|
||||
case .inactive:
|
||||
return "inactive"
|
||||
case .background:
|
||||
return "background"
|
||||
@unknown default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user