From 15649e27fd7cdae33a9322c2087d417b23cd67af Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Fri, 17 Oct 2025 15:03:08 -0400 Subject: [PATCH] ref: Refactor app state manager to not use protocol --- Sources/Sentry/SentryAppStartTracker.m | 4 +- .../SentryAppStartTrackingIntegration.m | 2 +- Sources/Sentry/SentryCrashIntegration.m | 2 +- Sources/Sentry/SentryDefaultAppStateManager.m | 83 +++++------------ Sources/Sentry/SentryDependencyContainer.m | 15 ++-- .../Sentry/SentryWatchdogTerminationLogic.m | 4 +- .../Sentry/SentryWatchdogTerminationTracker.m | 4 +- ...ryWatchdogTerminationTrackingIntegration.m | 4 +- .../HybridPublic/SentryDependencyContainer.h | 4 +- .../Sentry/include/SentryAppStartTracker.h | 4 +- .../include/SentryDefaultAppStateManager.h | 24 +---- Sources/Sentry/include/SentryPrivate.h | 1 + .../include/SentryWatchdogTerminationLogic.h | 4 +- .../SentryWatchdogTerminationTracker.h | 4 +- Sources/Swift/SentryAppState.swift | 8 +- Sources/Swift/SentryAppStateManager.swift | 90 +++++++++++++++++-- .../Helper/SentryAppStateManagerTests.swift | 9 +- .../Helper/SentryAppStateTests.swift | 2 +- .../SentryAppStartTrackerTests.swift | 7 +- ...entryWatchdogTerminationTrackerTests.swift | 6 +- ...gTerminationTrackingIntegrationTests.swift | 11 ++- 21 files changed, 151 insertions(+), 141 deletions(-) diff --git a/Sources/Sentry/SentryAppStartTracker.m b/Sources/Sentry/SentryAppStartTracker.m index 126af956327..4252ef9f20c 100644 --- a/Sources/Sentry/SentryAppStartTracker.m +++ b/Sources/Sentry/SentryAppStartTracker.m @@ -28,7 +28,7 @@ @interface SentryAppStartTracker () @property (nonatomic, strong, nullable) SentryAppState *previousAppState; @property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue; -@property (nonatomic, strong) id appStateManager; +@property (nonatomic, strong) SentryAppStateManager *appStateManager; @property (nonatomic, strong) SentryFramesTracker *framesTracker; @property (nonatomic, assign) BOOL wasInBackground; @property (nonatomic, strong) NSDate *didFinishLaunchingTimestamp; @@ -51,7 +51,7 @@ + (void)load } - (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper - appStateManager:(id)appStateManager + appStateManager:(SentryAppStateManager *)appStateManager framesTracker:(SentryFramesTracker *)framesTracker enablePreWarmedAppStartTracing:(BOOL)enablePreWarmedAppStartTracing { diff --git a/Sources/Sentry/SentryAppStartTrackingIntegration.m b/Sources/Sentry/SentryAppStartTrackingIntegration.m index 2911e954b7d..839c5cc7f7a 100644 --- a/Sources/Sentry/SentryAppStartTrackingIntegration.m +++ b/Sources/Sentry/SentryAppStartTrackingIntegration.m @@ -24,7 +24,7 @@ - (BOOL)installWithOptions:(SentryOptions *)options return NO; } - id appStateManager = + SentryAppStateManager *appStateManager = [SentryDependencyContainer sharedInstance].appStateManager; self.tracker = [[SentryAppStartTracker alloc] diff --git a/Sources/Sentry/SentryCrashIntegration.m b/Sources/Sentry/SentryCrashIntegration.m index b553f401fe0..25711e14ed5 100644 --- a/Sources/Sentry/SentryCrashIntegration.m +++ b/Sources/Sentry/SentryCrashIntegration.m @@ -90,7 +90,7 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options self.options = options; #if SENTRY_HAS_UIKIT - id appStateManager = + SentryAppStateManager *appStateManager = [SentryDependencyContainer sharedInstance].appStateManager; SentryWatchdogTerminationLogic *logic = [[SentryWatchdogTerminationLogic alloc] initWithOptions:options diff --git a/Sources/Sentry/SentryDefaultAppStateManager.m b/Sources/Sentry/SentryDefaultAppStateManager.m index 108562e707b..2bdaa58fca9 100644 --- a/Sources/Sentry/SentryDefaultAppStateManager.m +++ b/Sources/Sentry/SentryDefaultAppStateManager.m @@ -12,29 +12,31 @@ @interface SentryDefaultAppStateManager () -@property (nonatomic, strong) SentryOptions *options; -@property (nonatomic, strong) SentryCrashWrapper *crashWrapper; -@property (nonatomic, strong) SentryFileManager *fileManager; @property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue; @property (nonatomic, strong) id notificationCenterWrapper; +@property (nonatomic, copy) void (^storeCurrent)(void); +@property (nonatomic, copy) void (^updateTerminated)(void); +@property (nonatomic, copy) void (^updateSDKNotRunning)(void); +@property (nonatomic, copy) void (^updateActive)(BOOL); @property (nonatomic) NSInteger startCount; @end @implementation SentryDefaultAppStateManager -- (instancetype)initWithOptions:(SentryOptions *_Nullable)options - crashWrapper:(SentryCrashWrapper *)crashWrapper - fileManager:(SentryFileManager *_Nullable)fileManager - dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper - notificationCenterWrapper:(id)notificationCenterWrapper +- (instancetype)initWithStoreCurrent:(void (^)(void))storeCurrent + updateTerminated:(void (^)(void))updateTerminated + updateSDKNotRunning:(void (^)(void))updateSDKNotRunning + updateActive:(void (^)(BOOL))updateActive { if (self = [super init]) { - self.options = options; - self.crashWrapper = crashWrapper; - self.fileManager = fileManager; - self.dispatchQueue = dispatchQueueWrapper; - self.notificationCenterWrapper = notificationCenterWrapper; + self.dispatchQueue = SentryDependencyContainer.sharedInstance.dispatchQueueWrapper; + self.notificationCenterWrapper + = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; + self.storeCurrent = storeCurrent; + self.updateTerminated = updateTerminated; + self.updateSDKNotRunning = updateSDKNotRunning; + self.updateActive = updateActive; self.startCount = 0; } return self; @@ -65,7 +67,7 @@ - (void)start name:SentryWillTerminateNotification object:nil]; - [self storeCurrentAppState]; + self.storeCurrent(); } self.startCount += 1; @@ -84,8 +86,7 @@ - (void)stopWithForce:(BOOL)forceStop } if (forceStop) { - [self - updateAppStateInBackground:^(SentryAppState *appState) { appState.isSDKRunning = NO; }]; + [self.dispatchQueue dispatchAsyncWithBlock:^{ self.updateSDKNotRunning(); }]; self.startCount = 0; } else { @@ -130,7 +131,7 @@ - (void)dealloc */ - (void)didBecomeActive { - [self updateAppStateInBackground:^(SentryAppState *appState) { appState.isActive = YES; }]; + [self.dispatchQueue dispatchAsyncWithBlock:^{ self.updateActive(YES); }]; } /** @@ -139,7 +140,7 @@ - (void)didBecomeActive */ - (void)willResignActive { - [self updateAppStateInBackground:^(SentryAppState *appState) { appState.isActive = NO; }]; + [self.dispatchQueue dispatchAsyncWithBlock:^{ self.updateActive(NO); }]; } - (void)willTerminate @@ -147,51 +148,7 @@ - (void)willTerminate // The app is terminating so it is fine to do this on the main thread. // Furthermore, so users can manually post UIApplicationWillTerminateNotification and then call // exit(0), to avoid getting false watchdog terminations when using exit(0), see GH-1252. - [self updateAppState:^(SentryAppState *appState) { appState.wasTerminated = YES; }]; -} - -- (void)updateAppStateInBackground:(void (^)(SentryAppState *))block -{ - // We accept the tradeoff that the app state might not be 100% up to date over blocking the main - // thread. - [self.dispatchQueue dispatchAsyncWithBlock:^{ [self updateAppState:block]; }]; -} - -- (void)updateAppState:(void (^)(SentryAppState *))block -{ - @synchronized(self) { - SentryAppState *appState = [self.fileManager readAppState]; - if (appState != nil) { - block(appState); - [self.fileManager storeAppState:appState]; - } - } -} - -- (SentryAppState *)buildCurrentAppState -{ - // Is the current process being traced or not? If it is a debugger is attached. - bool isDebugging = self.crashWrapper.isBeingTraced; - - UIDevice *device = [UIDevice currentDevice]; - NSString *vendorId = [device.identifierForVendor UUIDString]; - - return [[SentryAppState alloc] initWithReleaseName:self.options.releaseName - osVersion:device.systemVersion - vendorId:vendorId - isDebugging:isDebugging - systemBootTimestamp:SentryDependencyContainer.sharedInstance - .sysctlWrapper.systemBootTimestamp]; -} - -- (nullable SentryAppState *)loadPreviousAppState -{ - return [self.fileManager readPreviousAppState]; -} - -- (void)storeCurrentAppState -{ - [self.fileManager storeAppState:[self buildCurrentAppState]]; + self.updateTerminated(); } #endif diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index c0f809285e7..9c64456ae2b 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -9,7 +9,6 @@ #import "SentrySwift.h" #import "SentrySystemWrapper.h" #import -#import #import #import #import @@ -58,9 +57,6 @@ @interface SentryFileManager () @end -@interface SentryDefaultAppStateManager () -@end - #if SENTRY_HAS_UIKIT @interface SentryWatchdogTerminationScopeObserver () @end @@ -218,14 +214,13 @@ - (nullable SentryFileManager *)fileManager SENTRY_THREAD_SANITIZER_DOUBLE_CHECK })); } -- (id)appStateManager SENTRY_THREAD_SANITIZER_DOUBLE_CHECKED_LOCK +- (SentryAppStateManager *)appStateManager SENTRY_THREAD_SANITIZER_DOUBLE_CHECKED_LOCK { SENTRY_LAZY_INIT(_appStateManager, - [[SentryDefaultAppStateManager alloc] initWithOptions:SentrySDKInternal.options - crashWrapper:self.crashWrapper - fileManager:self.fileManager - dispatchQueueWrapper:self.dispatchQueueWrapper - notificationCenterWrapper:self.notificationCenterWrapper]); + [[SentryAppStateManager alloc] initWithOptions:SentrySDKInternal.options + crashWrapper:self.crashWrapper + fileManager:self.fileManager + sysctlWrapper:self.sysctlWrapper]); } - (SentryThreadInspector *)threadInspector { diff --git a/Sources/Sentry/SentryWatchdogTerminationLogic.m b/Sources/Sentry/SentryWatchdogTerminationLogic.m index ca875279524..042cee62707 100644 --- a/Sources/Sentry/SentryWatchdogTerminationLogic.m +++ b/Sources/Sentry/SentryWatchdogTerminationLogic.m @@ -11,7 +11,7 @@ @interface SentryWatchdogTerminationLogic () @property (nonatomic, strong) SentryOptions *options; @property (nonatomic, strong) SentryCrashWrapper *crashAdapter; -@property (nonatomic, strong) id appStateManager; +@property (nonatomic, strong) SentryAppStateManager *appStateManager; @end @@ -19,7 +19,7 @@ @implementation SentryWatchdogTerminationLogic - (instancetype)initWithOptions:(SentryOptions *)options crashAdapter:(SentryCrashWrapper *)crashAdapter - appStateManager:(id)appStateManager + appStateManager:(SentryAppStateManager *)appStateManager { if (self = [super init]) { self.options = options; diff --git a/Sources/Sentry/SentryWatchdogTerminationTracker.m b/Sources/Sentry/SentryWatchdogTerminationTracker.m index e3d13bfdede..906b0eb1c4e 100644 --- a/Sources/Sentry/SentryWatchdogTerminationTracker.m +++ b/Sources/Sentry/SentryWatchdogTerminationTracker.m @@ -17,7 +17,7 @@ @interface SentryWatchdogTerminationTracker () @property (nonatomic, strong) SentryOptions *options; @property (nonatomic, strong) SentryWatchdogTerminationLogic *watchdogTerminationLogic; @property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue; -@property (nonatomic, strong) id appStateManager; +@property (nonatomic, strong) SentryAppStateManager *appStateManager; @property (nonatomic, strong) SentryFileManager *fileManager; @property (nonatomic, strong) SentryScopePersistentStore *scopePersistentStore; @@ -27,7 +27,7 @@ @implementation SentryWatchdogTerminationTracker - (instancetype)initWithOptions:(SentryOptions *)options watchdogTerminationLogic:(SentryWatchdogTerminationLogic *)watchdogTerminationLogic - appStateManager:(id)appStateManager + appStateManager:(SentryAppStateManager *)appStateManager dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper fileManager:(SentryFileManager *)fileManager scopePersistentStore:(SentryScopePersistentStore *)scopePersistentStore diff --git a/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m b/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m index 04c7364e7e9..ce0c9ba6b28 100644 --- a/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m +++ b/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m @@ -22,7 +22,7 @@ @interface SentryWatchdogTerminationTrackingIntegration () anrTracker; @property (nullable, nonatomic, copy) NSString *testConfigurationFilePath; -@property (nonatomic, strong) id appStateManager; +@property (nonatomic, strong) SentryAppStateManager *appStateManager; @end @@ -56,7 +56,7 @@ - (BOOL)installWithOptions:(SentryOptions *)options attributes:attributes]; SentryFileManager *fileManager = [[[SentrySDKInternal currentHub] getClient] fileManager]; - id appStateManager = + SentryAppStateManager *appStateManager = [SentryDependencyContainer sharedInstance].appStateManager; SentryCrashWrapper *crashWrapper = [SentryDependencyContainer sharedInstance].crashWrapper; SentryWatchdogTerminationLogic *logic = diff --git a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h index 3a18667a387..c0294b37910 100644 --- a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h +++ b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h @@ -20,12 +20,12 @@ @class SentryFileIOTracker; @class SentryScopePersistentStore; @class SentryOptions; +@class SentryAppStateManager; @class SentrySessionTracker; @class SentryGlobalEventProcessor; @class SentryThreadInspector; @class SentryReachability; -@protocol SentryAppStateManager; @protocol SentryANRTracker; @protocol SentryRandomProtocol; @protocol SentryCurrentDateProvider; @@ -99,7 +99,7 @@ SENTRY_NO_INIT #pragma mark - Lazy Dependencies @property (nonatomic, strong, nullable) SentryFileManager *fileManager; -@property (nonatomic, strong) id appStateManager; +@property (nonatomic, strong) SentryAppStateManager *appStateManager; @property (nonatomic, strong, readonly) SentryThreadInspector *threadInspector; @property (nonatomic, strong, readonly) SentryFileIOTracker *fileIOTracker; @property (nonatomic, strong) SentryCrashSwift *crashReporter; diff --git a/Sources/Sentry/include/SentryAppStartTracker.h b/Sources/Sentry/include/SentryAppStartTracker.h index e083c93e63e..b097c27421e 100644 --- a/Sources/Sentry/include/SentryAppStartTracker.h +++ b/Sources/Sentry/include/SentryAppStartTracker.h @@ -3,7 +3,7 @@ #if SENTRY_HAS_UIKIT @class SentryDispatchQueueWrapper; -@protocol SentryAppStateManager; +@class SentryAppStateManager; @class SentryFramesTracker; NS_ASSUME_NONNULL_BEGIN @@ -20,7 +20,7 @@ SENTRY_NO_INIT @property (nonatomic) BOOL isRunning; - (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper - appStateManager:(id)appStateManager + appStateManager:(SentryAppStateManager *)appStateManager framesTracker:(SentryFramesTracker *)framesTracker enablePreWarmedAppStartTracing:(BOOL)enablePreWarmedAppStartTracing; diff --git a/Sources/Sentry/include/SentryDefaultAppStateManager.h b/Sources/Sentry/include/SentryDefaultAppStateManager.h index 6e3dda9eec5..5a64fce79e8 100644 --- a/Sources/Sentry/include/SentryDefaultAppStateManager.h +++ b/Sources/Sentry/include/SentryDefaultAppStateManager.h @@ -15,11 +15,10 @@ SENTRY_NO_INIT @property (nonatomic, readonly) NSInteger startCount; -- (instancetype)initWithOptions:(SentryOptions *_Nullable)options - crashWrapper:(SentryCrashWrapper *)crashWrapper - fileManager:(SentryFileManager *_Nullable)fileManager - dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper - notificationCenterWrapper:(id)notificationCenterWrapper; +- (instancetype)initWithStoreCurrent:(void (^)(void))storeCurrent + updateTerminated:(void (^)(void))updateTerminated + updateSDKNotRunning:(void (^)(void))updateSDKNotRunning + updateActive:(void (^)(BOOL))updateActive; #if SENTRY_HAS_UIKIT @@ -27,21 +26,6 @@ SENTRY_NO_INIT - (void)stop; - (void)stopWithForce:(BOOL)forceStop; -/** - * Builds the current app state. - * @discussion The systemBootTimestamp is calculated by taking the current time and subtracting - * @c NSProcesInfo.systemUptime . @c NSProcesInfo.systemUptime returns the amount of time the system - * has been awake since the last time it was restarted. This means This is a good enough - * approximation about the timestamp the system booted. - */ -- (SentryAppState *)buildCurrentAppState; - -- (nullable SentryAppState *)loadPreviousAppState; - -- (void)storeCurrentAppState; - -- (void)updateAppState:(void (^)(SentryAppState *))block; - #endif @end diff --git a/Sources/Sentry/include/SentryPrivate.h b/Sources/Sentry/include/SentryPrivate.h index 1dc42519f73..07ab3071947 100644 --- a/Sources/Sentry/include/SentryPrivate.h +++ b/Sources/Sentry/include/SentryPrivate.h @@ -29,6 +29,7 @@ #import "SentryCrashMonitor_AppState.h" #import "SentryCrashMonitor_System.h" #import "SentryDateUtils.h" +#import "SentryDefaultAppStateManager.h" #import "SentryDefaultThreadInspector.h" #import "SentryDependencyContainerSwiftHelper.h" #import "SentryDeviceContextKeys.h" diff --git a/Sources/Sentry/include/SentryWatchdogTerminationLogic.h b/Sources/Sentry/include/SentryWatchdogTerminationLogic.h index 997db10424c..ee0add889ba 100644 --- a/Sources/Sentry/include/SentryWatchdogTerminationLogic.h +++ b/Sources/Sentry/include/SentryWatchdogTerminationLogic.h @@ -3,7 +3,7 @@ #if SENTRY_HAS_UIKIT @class SentryAppState; -@protocol SentryAppStateManager; +@class SentryAppStateManager; @class SentryCrashWrapper; @class SentryFileManager; @class SentryOptions; @@ -15,7 +15,7 @@ SENTRY_NO_INIT - (instancetype)initWithOptions:(SentryOptions *)options crashAdapter:(SentryCrashWrapper *)crashAdapter - appStateManager:(id)appStateManager; + appStateManager:(SentryAppStateManager *)appStateManager; - (BOOL)isWatchdogTermination; diff --git a/Sources/Sentry/include/SentryWatchdogTerminationTracker.h b/Sources/Sentry/include/SentryWatchdogTerminationTracker.h index 54f152fd68e..50e3e329f7e 100644 --- a/Sources/Sentry/include/SentryWatchdogTerminationTracker.h +++ b/Sources/Sentry/include/SentryWatchdogTerminationTracker.h @@ -1,6 +1,6 @@ #import "SentryDefines.h" -@protocol SentryAppStateManager; +@class SentryAppStateManager; @class SentryDispatchQueueWrapper; @class SentryFileManager; @class SentryOptions; @@ -25,7 +25,7 @@ SENTRY_NO_INIT - (instancetype)initWithOptions:(SentryOptions *)options watchdogTerminationLogic:(SentryWatchdogTerminationLogic *)watchdogTerminationLogic - appStateManager:(id)appStateManager + appStateManager:(SentryAppStateManager *)appStateManager dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper fileManager:(SentryFileManager *)fileManager scopePersistentStore:(SentryScopePersistentStore *)scopeStore; diff --git a/Sources/Swift/SentryAppState.swift b/Sources/Swift/SentryAppState.swift index 1779f6f5080..2ed5bb894da 100644 --- a/Sources/Swift/SentryAppState.swift +++ b/Sources/Swift/SentryAppState.swift @@ -5,7 +5,7 @@ import Foundation public private(set) var releaseName: String? public private(set) var osVersion: String - public private(set) var vendorId: String + public private(set) var vendorId: String? public private(set) var isDebugging: Bool /// The boot time of the system rounded down to seconds. As the precision of the serialization is @@ -22,7 +22,7 @@ import Foundation public var isANROngoing: Bool public var isSDKRunning: Bool - public init(releaseName: String?, osVersion: String, vendorId: String, isDebugging: Bool, systemBootTimestamp: Date) { + public init(releaseName: String?, osVersion: String, vendorId: String?, isDebugging: Bool, systemBootTimestamp: Date) { self.releaseName = releaseName self.osVersion = osVersion self.vendorId = vendorId @@ -119,7 +119,9 @@ import Foundation data["release_name"] = releaseName } data["os_version"] = self.osVersion - data["vendor_id"] = self.vendorId + if let vendorId = self.vendorId { + data["vendor_id"] = vendorId + } data["is_debugging"] = NSNumber(value: self.isDebugging) data["system_boot_timestamp"] = sentry_toIso8601String(self.systemBootTimestamp) data["is_active"] = NSNumber(value: self.isActive) diff --git a/Sources/Swift/SentryAppStateManager.swift b/Sources/Swift/SentryAppStateManager.swift index feb8c210120..3a321f7fe09 100644 --- a/Sources/Swift/SentryAppStateManager.swift +++ b/Sources/Swift/SentryAppStateManager.swift @@ -1,9 +1,72 @@ -@_spi(Private) @objc public protocol SentryAppStateManager { - var startCount: Int { get } +@_implementationOnly import _SentryPrivate + +#if (os(iOS) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT +import UIKit +#endif + +@_spi(Private) @objc public final class SentryAppStateManager: NSObject { - func start() - func stop() - func stop(withForce force: Bool) + private let options: Options? + private let crashWrapper: SentryCrashWrapper + private let fileManager: SentryFileManager? +#if (os(iOS) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT + private let _updateAppState: (@escaping (SentryAppState) -> Void) -> Void + private let _buildCurrentAppState: () -> SentryAppState + private let helper: SentryDefaultAppStateManager +#endif + + @objc public init(options: Options?, crashWrapper: SentryCrashWrapper, fileManager: SentryFileManager?, sysctlWrapper: SentrySysctl) { + self.options = options + self.crashWrapper = crashWrapper + self.fileManager = fileManager +#if (os(iOS) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT + let lock = NSRecursiveLock() + let buildCurrentAppState = { + // Is the current process being traced or not? If it is a debugger is attached. + let isDebugging = crashWrapper.isBeingTraced + + let device = UIDevice.current + let vendorId = device.identifierForVendor?.uuidString + + return SentryAppState(releaseName: options?.releaseName, osVersion: device.systemVersion, vendorId: vendorId, isDebugging: isDebugging, systemBootTimestamp: sysctlWrapper.systemBootTimestamp) + } + _buildCurrentAppState = buildCurrentAppState + let updateAppState: (@escaping (SentryAppState) -> Void) -> Void = { block in + lock.synchronized { + let appState = fileManager?.readAppState() + if let appState { + block(appState) + fileManager?.store(appState) + } + } + } + _updateAppState = updateAppState + helper = SentryDefaultAppStateManager(storeCurrent: { + fileManager?.store(buildCurrentAppState()) + }, updateTerminated: { + updateAppState { $0.wasTerminated = true } + }, updateSDKNotRunning: { + updateAppState { $0.isSDKRunning = false } + }, updateActive: { active in + updateAppState { $0.isActive = active } + }) +#endif + } + +#if (os(iOS) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT + var startCount: Int { + helper.startCount + } + + @objc public func start() { + helper.start() + } + @objc public func stop() { + helper.stop() + } + @objc public func stop(withForce force: Bool) { + helper.stop(withForce: force) + } /** * Builds the current app state. @@ -12,11 +75,20 @@ * has been awake since the last time it was restarted. This means This is a good enough * approximation about the timestamp the system booted. */ - func buildCurrentAppState() -> SentryAppState + @objc public func buildCurrentAppState() -> SentryAppState { + _buildCurrentAppState() + } - func loadPreviousAppState() -> SentryAppState? + @objc public func loadPreviousAppState() -> SentryAppState? { + fileManager?.readPreviousAppState() + } - func storeCurrentAppState() + func storeCurrentAppState() { + fileManager?.store(buildCurrentAppState()) + } - func updateAppState(_ block: @escaping (SentryAppState) -> Void) + @objc public func updateAppState(_ block: @escaping (SentryAppState) -> Void) { + _updateAppState(block) + } +#endif } diff --git a/Tests/SentryTests/Helper/SentryAppStateManagerTests.swift b/Tests/SentryTests/Helper/SentryAppStateManagerTests.swift index 9783a6f6bb4..f198afbb89e 100644 --- a/Tests/SentryTests/Helper/SentryAppStateManagerTests.swift +++ b/Tests/SentryTests/Helper/SentryAppStateManagerTests.swift @@ -1,4 +1,4 @@ -@_spi(Private) import Sentry +@_spi(Private) @testable import Sentry @_spi(Private) import SentryTestUtils import XCTest @@ -28,12 +28,13 @@ class SentryAppStateManagerTests: XCTestCase { func getSut() -> SentryAppStateManager { SentryDependencyContainer.sharedInstance().sysctlWrapper = TestSysctl() - return SentryDefaultAppStateManager( + SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = TestSentryDispatchQueueWrapper() + SentryDependencyContainer.sharedInstance().notificationCenterWrapper = notificationCenterWrapper + return SentryAppStateManager( options: options, crashWrapper: TestSentryCrashWrapper(processInfoWrapper: ProcessInfo.processInfo), fileManager: fileManager, - dispatchQueueWrapper: TestSentryDispatchQueueWrapper(), - notificationCenterWrapper: notificationCenterWrapper + sysctlWrapper: SentryDependencyContainer.sharedInstance().sysctlWrapper ) } } diff --git a/Tests/SentryTests/Helper/SentryAppStateTests.swift b/Tests/SentryTests/Helper/SentryAppStateTests.swift index 129eb30bbd2..4665922674a 100644 --- a/Tests/SentryTests/Helper/SentryAppStateTests.swift +++ b/Tests/SentryTests/Helper/SentryAppStateTests.swift @@ -41,7 +41,7 @@ class SentryAppStateTests: XCTestCase { let dict = [ "release_name": releaseName, "os_version": appState.osVersion, - "vendor_id": appState.vendorId, + "vendor_id": appState.vendorId ?? "", "is_debugging": appState.isDebugging, "system_boot_timestamp": sentry_toIso8601String(appState.systemBootTimestamp), "is_active": appState.isActive, diff --git a/Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift index 33fbb29d036..1c4416abe13 100644 --- a/Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift @@ -40,13 +40,12 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase { ) SentryDependencyContainer.sharedInstance().sysctlWrapper = sysctl - - appStateManager = SentryDefaultAppStateManager( + SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = dispatchQueue + appStateManager = SentryAppStateManager( options: options, crashWrapper: crashWrapper, fileManager: fileManager, - dispatchQueueWrapper: dispatchQueue, - notificationCenterWrapper: NotificationCenter.default + sysctlWrapper: sysctl ) framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper, dateProvider: currentDate, dispatchQueueWrapper: TestSentryDispatchQueueWrapper(), diff --git a/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationTrackerTests.swift b/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationTrackerTests.swift index 33e12a2295a..4ba07a4f48e 100644 --- a/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationTrackerTests.swift +++ b/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationTrackerTests.swift @@ -57,12 +57,12 @@ class SentryWatchdogTerminationTrackerTests: NotificationCenterTestCase { } func getSut(fileManager: SentryFileManager) throws -> SentryWatchdogTerminationTracker { - let appStateManager = SentryDefaultAppStateManager( + SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = dispatchQueue + let appStateManager = SentryAppStateManager( options: options, crashWrapper: crashWrapper, fileManager: fileManager, - dispatchQueueWrapper: self.dispatchQueue, - notificationCenterWrapper: NotificationCenter.default + sysctlWrapper: sysctl ) let logic = SentryWatchdogTerminationLogic( options: options, diff --git a/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationTrackingIntegrationTests.swift index 38875e5dfee..0eb3f949660 100644 --- a/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationTrackingIntegrationTests.swift @@ -4,8 +4,6 @@ @_spi(Private) import SentryTestUtils import XCTest -@_spi(Private) extension SentryDefaultAppStateManager: SentryAppStateManager { } - class SentryWatchdogTerminationIntegrationTests: XCTestCase { private static let dsn = TestConstants.dsnForTestCase(type: SentryWatchdogTerminationIntegrationTests.self) @@ -18,7 +16,7 @@ class SentryWatchdogTerminationIntegrationTests: XCTestCase { let watchdogTerminationAttributesProcessor: TestSentryWatchdogTerminationAttributesProcessor let hub: SentryHub let scope: Scope - let appStateManager: SentryDefaultAppStateManager + let appStateManager: SentryAppStateManager convenience init() throws { let options = Options() @@ -51,12 +49,13 @@ class SentryWatchdogTerminationIntegrationTests: XCTestCase { container.fileManager = fileManager let notificationCenterWrapper = TestNSNotificationCenterWrapper() - appStateManager = SentryDefaultAppStateManager( + SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = dispatchQueueWrapper + SentryDependencyContainer.sharedInstance().notificationCenterWrapper = notificationCenterWrapper + appStateManager = SentryAppStateManager( options: options, crashWrapper: crashWrapper, fileManager: fileManager, - dispatchQueueWrapper: dispatchQueueWrapper, - notificationCenterWrapper: notificationCenterWrapper + sysctlWrapper: SentryDependencyContainer.sharedInstance().sysctlWrapper ) container.appStateManager = appStateManager appStateManager.start()