Swiftify didFinishLaunchingWithOptions

This commit is contained in:
Max Radermacher 2023-04-03 14:58:19 -07:00 committed by GitHub
parent 7651543b90
commit 53351b03fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 185 additions and 214 deletions

View File

@ -19,17 +19,13 @@ extern NSString *const kAppLaunchesAttemptedKey;
// This public interface is only needed for Swift bridging.
@property (nonatomic, readonly) NSTimeInterval launchStartedAt;
@property (nonatomic, readwrite) NSTimeInterval launchStartedAt;
@property (nonatomic, readwrite) BOOL areVersionMigrationsComplete;
@property (nonatomic, readwrite) BOOL didAppLaunchFail;
@property (nonatomic, readwrite) BOOL shouldKillAppWhenBackgrounded;
- (BOOL)launchToHomeScreenWithLaunchOptions:(NSDictionary *_Nullable)launchOptions
instrumentsMonitorId:(unsigned long long)monitorId
isEnvironmentAlreadySetUp:(BOOL)isEnvironmentAlreadySetUp;
@end
NS_ASSUME_NONNULL_END

View File

@ -83,9 +83,6 @@ static void uncaughtExceptionHandler(NSException *exception)
}
@interface AppDelegate () <UNUserNotificationCenterDelegate>
@property (nonatomic, readwrite) NSTimeInterval launchStartedAt;
@end
#pragma mark -
@ -155,175 +152,14 @@ static void uncaughtExceptionHandler(NSException *exception)
OWSLogFlush();
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions
{
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
// This should be the first thing we do.
SetCurrentAppContext([MainAppContext new], NO);
self.launchStartedAt = CACurrentMediaTime();
[BenchManager startEventWithTitle:@"Presenting HomeView" eventId:@"AppStart" logInProduction:TRUE];
BOOL isLoggingEnabled;
[InstrumentsMonitor enable];
unsigned long long monitorId = [InstrumentsMonitor startSpanWithCategory:@"appstart"
parent:@"application"
name:@"didFinishLaunchingWithOptions"];
#ifdef DEBUG
// Specified at Product -> Scheme -> Edit Scheme -> Test -> Arguments -> Environment to avoid things like
// the phone directory being looked up during tests.
isLoggingEnabled = TRUE;
[DebugLogger.shared enableTTYLogging];
#else
isLoggingEnabled = OWSPreferences.isLoggingEnabled;
#endif
if (isLoggingEnabled) {
[DebugLogger.shared enableFileLogging];
}
if (SSKDebugFlags.audibleErrorLogging) {
[DebugLogger.shared enableErrorReporting];
}
[DebugLogger configureSwiftLogging];
#ifdef DEBUG
[SSKFeatureFlags logFlags];
[SSKDebugFlags logFlags];
#endif
OWSLogWarn(@"application: didFinishLaunchingWithOptions.");
[Cryptography seedRandom];
// This *must* happen before we try and access or verify the database, since we
// may be in a state where the database has been partially restored from transfer
// (e.g. the key was replaced, but the database files haven't been moved into place)
__block BOOL didDeviceTransferRestoreSucceed = YES;
[BenchManager benchWithTitle:@"Slow device transfer service launch"
logIfLongerThan:0.01
logInProduction:YES
block:^{ didDeviceTransferRestoreSucceed = [DeviceTransferService.shared launchCleanup]; }];
// XXX - careful when moving this. It must happen before we load GRDB.
[self verifyDBKeysAvailableBeforeBackgroundLaunch];
[InstrumentsMonitor trackEventWithName:@"AppStart"];
[AppVersion shared];
// We need to do this _after_ we set up logging, when the keychain is unlocked,
// but before we access the database, files on disk, or NSUserDefaults.
LaunchFailure launchFailure =
[self launchFailureWithDidDeviceTransferRestoreSucceed:didDeviceTransferRestoreSucceed];
if (launchFailure != LaunchFailureNone) {
[InstrumentsMonitor stopSpanWithCategory:@"appstart" hash:monitorId];
OWSLogInfo(@"application: didFinishLaunchingWithOptions failed.");
[self showUIForLaunchFailure:launchFailure];
return YES;
}
[self launchToHomeScreenWithLaunchOptions:launchOptions
instrumentsMonitorId:monitorId
isEnvironmentAlreadySetUp:NO];
[self handleDidFinishLaunchingWithLaunchOptions:launchOptions];
return YES;
}
- (BOOL)launchToHomeScreenWithLaunchOptions:(NSDictionary *_Nullable)launchOptions
instrumentsMonitorId:(unsigned long long)monitorId
isEnvironmentAlreadySetUp:(BOOL)isEnvironmentAlreadySetUp
{
[self setupNSEInteroperation];
if (CurrentAppContext().isRunningTests) {
[InstrumentsMonitor stopSpanWithCategory:@"appstart" hash:monitorId];
return YES;
}
NSInteger appLaunchesAttempted = [[CurrentAppContext() appUserDefaults] integerForKey:kAppLaunchesAttemptedKey];
[[CurrentAppContext() appUserDefaults] setInteger:appLaunchesAttempted + 1 forKey:kAppLaunchesAttemptedKey];
AppReadinessRunNowOrWhenMainAppDidBecomeReadyAsync(
^{ [[CurrentAppContext() appUserDefaults] removeObjectForKey:kAppLaunchesAttemptedKey]; });
if (!isEnvironmentAlreadySetUp) {
[AppDelegate setUpMainAppEnvironmentWithCompletion:^(NSError *_Nullable error) {
OWSAssertIsOnMainThread();
if (error != nil) {
OWSFailDebug(@"Error: %@", error);
[self showUIForLaunchFailure:LaunchFailureCouldNotLoadDatabase];
} else {
[self versionMigrationsDidComplete];
}
}];
}
[UIUtil setupSignalAppearance];
UIWindow *mainWindow = self.window;
if (mainWindow == nil) {
mainWindow = [OWSWindow new];
self.window = mainWindow;
CurrentAppContext().mainWindow = mainWindow;
}
// Show LoadingViewController until the async database view registrations are complete.
mainWindow.rootViewController = [LoadingViewController new];
[mainWindow makeKeyAndVisible];
// This must happen in appDidFinishLaunching or earlier to ensure we don't
// miss notifications.
// Setting the delegate also seems to prevent us from getting the legacy notification
// notification callbacks upon launch e.g. 'didReceiveLocalNotification'
UNUserNotificationCenter.currentNotificationCenter.delegate = self;
// Accept push notification when app is not open
NSDictionary *remoteNotification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotification) {
OWSLogInfo(@"Application was launched by tapping a push notification.");
[self processRemoteNotification:remoteNotification];
}
[OWSScreenLockUI.shared setupWithRootWindow:self.window];
[[OWSWindowManager shared] setupWithRootWindow:self.window
screenBlockingWindow:OWSScreenLockUI.shared.screenBlockingWindow];
[OWSScreenLockUI.shared startObserving];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(storageIsReady)
name:StorageIsReadyNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registrationStateDidChange)
name:NSNotificationNameRegistrationStateDidChange
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registrationLockDidChange:)
name:NSNotificationName_2FAStateDidChange
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(spamChallenge:)
name:SpamChallengeResolver.NeedsCaptchaNotification
object:nil];
OWSLogInfo(@"application: didFinishLaunchingWithOptions completed.");
OWSLogInfo(@"launchOptions: %@.", launchOptions);
[OWSAnalytics appLaunchDidBegin];
[InstrumentsMonitor stopSpanWithCategory:@"appstart" hash:monitorId];
return YES;
}
- (void)spamChallenge:(NSNotification *)notification
{
UIViewController *fromVC = UIApplication.sharedApplication.frontmostViewController;
[SpamCaptchaViewController presentActionSheetFrom:fromVC];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
OWSAssertIsOnMainThread();
@ -883,17 +719,4 @@ static void uncaughtExceptionHandler(NSException *exception)
});
}
- (void)storageIsReady
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"storageIsReady");
[self checkIfAppIsReady];
}
- (void)registrationLockDidChange:(NSNotification *)notification
{
[self enableBackgroundRefreshIfNecessary];
}
@end

View File

@ -5,6 +5,7 @@
import Foundation
import SignalMessaging
import SignalUI
@objc
enum LaunchFailure: UInt, CustomStringConvertible {
@ -42,7 +43,157 @@ enum LaunchFailure: UInt, CustomStringConvertible {
extension AppDelegate {
// MARK: - App launch
static func setUpMainAppEnvironment() -> Promise<Void> {
@objc
func handleDidFinishLaunching(launchOptions: [UIApplication.LaunchOptionsKey: Any]) {
// This should be the first thing we do.
SetCurrentAppContext(MainAppContext(), false)
self.launchStartedAt = CACurrentMediaTime()
BenchManager.startEvent(title: "Presenting HomeView", eventId: "AppStart", logInProduction: true)
InstrumentsMonitor.enable()
let monitorId = InstrumentsMonitor.startSpan(category: "appstart", parent: "application", name: "didFinishLaunchingWithOptions")
defer { InstrumentsMonitor.stopSpan(category: "appstart", hash: monitorId) }
enableLoggingIfNeeded()
#if DEBUG
FeatureFlags.logFlags()
DebugFlags.logFlags()
#endif
Logger.warn("application: didFinishLaunchingWithOptions.")
defer { Logger.info("application: didFinishLaunchingWithOptions completed.") }
Cryptography.seedRandom()
// This *must* happen before we try and access or verify the database,
// since we may be in a state where the database has been partially
// restored from transfer (e.g. the key was replaced, but the database
// files haven't been moved into place)
let didDeviceTransferRestoreSucceed = Bench(
title: "Slow device transfer service launch",
logIfLongerThan: 0.01,
logInProduction: true,
block: { DeviceTransferService.shared.launchCleanup() }
)
// XXX - careful when moving this. It must happen before we load GRDB.
verifyDBKeysAvailableBeforeBackgroundLaunch()
InstrumentsMonitor.trackEvent(name: "AppStart")
AppVersion.shared()
// We need to do this _after_ we set up logging, when the keychain is unlocked,
// but before we access the database, files on disk, or NSUserDefaults.
let launchFailure = launchFailure(didDeviceTransferRestoreSucceed: didDeviceTransferRestoreSucceed)
guard launchFailure == .none else {
showUI(forLaunchFailure: launchFailure)
return
}
launchToHomeScreen(launchOptions: launchOptions, isEnvironmentAlreadySetUp: false)
}
private func enableLoggingIfNeeded() {
let isLoggingEnabled: Bool
#if DEBUG
isLoggingEnabled = true
DebugLogger.shared().enableTTYLogging()
#else
isLoggingEnabled = OWSPreferences.isLoggingEnabled()
#endif
if isLoggingEnabled {
DebugLogger.shared().enableFileLogging()
}
if DebugFlags.audibleErrorLogging {
DebugLogger.shared().enableErrorReporting()
}
DebugLogger.configureSwiftLogging()
}
private func launchToHomeScreen(launchOptions: [UIApplication.LaunchOptionsKey: Any]?, isEnvironmentAlreadySetUp: Bool) {
setupNSEInteroperation()
if CurrentAppContext().isRunningTests {
return
}
let userDefaults = CurrentAppContext().appUserDefaults()
let appLaunchesAttempted = userDefaults.integer(forKey: kAppLaunchesAttemptedKey)
userDefaults.set(appLaunchesAttempted + 1, forKey: kAppLaunchesAttemptedKey)
AppReadiness.runNowOrWhenMainAppDidBecomeReadyAsync { userDefaults.removeObject(forKey: kAppLaunchesAttemptedKey) }
if !isEnvironmentAlreadySetUp {
Self.setUpMainAppEnvironment().done(on: DispatchQueue.main) {
self.versionMigrationsDidComplete()
}.catch(on: DispatchQueue.main) { error in
owsFailDebug("Error: \(error)")
self.showUI(forLaunchFailure: .couldNotLoadDatabase)
}
}
UIUtil.setupSignalAppearance()
let mainWindow: UIWindow = self.window ?? {
let result = OWSWindow()
self.window = result
CurrentAppContext().mainWindow = result
return result
}()
// Show LoadingViewController until the async database view registrations are complete.
mainWindow.rootViewController = LoadingViewController()
mainWindow.makeKeyAndVisible()
// This must happen in appDidFinishLaunching or earlier to ensure we don't
// miss notifications. Setting the delegate also seems to prevent us from
// getting the legacy notification notification callbacks upon launch e.g.
// 'didReceiveLocalNotification'
UNUserNotificationCenter.current().delegate = self
if let remoteNotification = launchOptions?[.remoteNotification] as? NSDictionary {
Logger.info("Application was launched by tapping a push notification.")
processRemoteNotification(remoteNotification)
}
let screenLockUI = OWSScreenLockUI.shared()
let windowManager = OWSWindowManager.shared
screenLockUI.setup(withRootWindow: mainWindow)
windowManager.setup(withRootWindow: mainWindow, screenBlockingWindow: screenLockUI.screenBlockingWindow)
screenLockUI.startObserving()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(
self,
selector: #selector(storageIsReady),
name: .StorageIsReady,
object: nil
)
notificationCenter.addObserver(
self,
selector: #selector(registrationStateDidChange),
name: .registrationStateDidChange,
object: nil
)
notificationCenter.addObserver(
self,
selector: #selector(registrationLockDidChange),
name: Notification.Name(NSNotificationName_2FAStateDidChange),
object: nil
)
notificationCenter.addObserver(
self,
selector: #selector(spamChallenge),
name: SpamChallengeResolver.NeedsCaptchaNotification,
object: nil
)
OWSAnalytics.appLaunchDidBegin()
}
private static func setUpMainAppEnvironment() -> Promise<Void> {
let (promise, future) = Promise<Void>.pending()
AppSetup.setupEnvironment(
paymentsEvents: PaymentsEventsMainApp(),
@ -64,18 +215,7 @@ extension AppDelegate {
return promise
}
@objc(setUpMainAppEnvironmentWithCompletion:)
static func setUpMainAppEnvironment(completion: @escaping (Error?) -> Void) {
firstly(on: DispatchQueue.main) {
setUpMainAppEnvironment()
}.done(on: DispatchQueue.main) {
completion(nil)
}.catch(on: DispatchQueue.main) { error in
completion(error)
}
}
func checkSomeDiskSpaceAvailable() -> Bool {
private func checkSomeDiskSpaceAvailable() -> Bool {
let tempDir = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
.path
@ -89,8 +229,7 @@ extension AppDelegate {
return succeededCreatingDir
}
@objc
func setupNSEInteroperation() {
private func setupNSEInteroperation() {
Logger.info("")
// We immediately post a notification letting the NSE know the main app has launched.
@ -116,16 +255,14 @@ extension AppDelegate {
}
}
@objc
func versionMigrationsDidComplete() {
private func versionMigrationsDidComplete() {
AssertIsOnMainThread()
Logger.info("versionMigrationsDidComplete")
areVersionMigrationsComplete = true
checkIfAppIsReady()
}
@objc
func checkIfAppIsReady() {
private func checkIfAppIsReady() {
AssertIsOnMainThread()
// If launch failed, the app will never be ready.
@ -224,7 +361,6 @@ extension AppDelegate {
)
}
@objc
func enableBackgroundRefreshIfNecessary() {
AppReadiness.runNowOrWhenAppDidBecomeReadySync {
let interval: TimeInterval
@ -241,8 +377,7 @@ extension AppDelegate {
}
/// The user must unlock the device once after reboot before the database encryption key can be accessed.
@objc
func verifyDBKeysAvailableBeforeBackgroundLaunch() {
private func verifyDBKeysAvailableBeforeBackgroundLaunch() {
guard UIApplication.shared.applicationState == .background else {
return
}
@ -284,8 +419,7 @@ extension AppDelegate {
// MARK: - Launch failures
@objc
func launchFailure(didDeviceTransferRestoreSucceed: Bool) -> LaunchFailure {
private func launchFailure(didDeviceTransferRestoreSucceed: Bool) -> LaunchFailure {
guard checkSomeDiskSpaceAvailable() else {
return .lowStorageSpaceAvailable
}
@ -330,8 +464,7 @@ extension AppDelegate {
return .none
}
@objc(showUIForLaunchFailure:)
func showUI(forLaunchFailure launchFailure: LaunchFailure) {
private func showUI(forLaunchFailure launchFailure: LaunchFailure) {
Logger.info("launchFailure: \(launchFailure)")
// Disable normal functioning of app.
@ -414,7 +547,7 @@ extension AppDelegate {
// Pretend we didn't fail!
self.didAppLaunchFail = false
self.versionMigrationsDidComplete()
self.launchToHomeScreen(launchOptions: nil, instrumentsMonitorId: 0, isEnvironmentAlreadySetUp: true)
self.launchToHomeScreen(launchOptions: nil, isEnvironmentAlreadySetUp: true)
}
)
@ -522,7 +655,7 @@ extension AppDelegate {
// Pretend we didn't fail!
self.didAppLaunchFail = false
self.checkIfAppIsReady()
self.launchToHomeScreen(launchOptions: nil, instrumentsMonitorId: 0, isEnvironmentAlreadySetUp: false)
self.launchToHomeScreen(launchOptions: nil, isEnvironmentAlreadySetUp: false)
}
for action in actions {
@ -620,7 +753,15 @@ extension AppDelegate {
// MARK: - Events
@objc
func registrationStateDidChange() {
private func storageIsReady() {
AssertIsOnMainThread()
Logger.info("storageIsReady")
checkIfAppIsReady()
}
@objc
private func registrationStateDidChange() {
AssertIsOnMainThread()
Logger.info("registrationStateDidChange")
@ -646,6 +787,16 @@ extension AppDelegate {
Self.updateApplicationShortcutItems(isRegisteredAndReady: isRegisteredAndReady)
}
@objc
private func registrationLockDidChange() {
enableBackgroundRefreshIfNecessary()
}
@objc
private func spamChallenge() {
SpamCaptchaViewController.presentActionSheet(from: UIApplication.shared.frontmostViewController!)
}
// MARK: - Utilities
@objc

View File

@ -18,6 +18,7 @@
#import "FingerprintViewController.h"
#import "OWSLinkDeviceViewController.h"
#import "OWSQuotedMessageView.h"
#import "OWSScreenLockUI.h"
#import "OWSWindowManager.h"
#import "RecipientPickerViewController.h"
#import "RegistrationUtils.h"