diff --git a/FirebasePerformance/Sources/AppActivity/FPRTraceBackgroundActivityTracker.m b/FirebasePerformance/Sources/AppActivity/FPRTraceBackgroundActivityTracker.m index 5c7db89c9fe..7e5878d4725 100644 --- a/FirebasePerformance/Sources/AppActivity/FPRTraceBackgroundActivityTracker.m +++ b/FirebasePerformance/Sources/AppActivity/FPRTraceBackgroundActivityTracker.m @@ -23,6 +23,8 @@ @interface FPRTraceBackgroundActivityTracker () @property(nonatomic, readwrite) FPRTraceState traceBackgroundState; +- (void)registerNotificationObservers; + @end @implementation FPRTraceBackgroundActivityTracker @@ -35,21 +37,26 @@ - (instancetype)init { } else { _traceBackgroundState = FPRTraceStateForegroundOnly; } + __strong typeof(self) strongSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:UIApplicationDidBecomeActiveNotification - object:[UIApplication sharedApplication]]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidEnterBackground:) - name:UIApplicationDidEnterBackgroundNotification - object:[UIApplication sharedApplication]]; + [strongSelf registerNotificationObservers]; }); } return self; } +- (void)registerNotificationObservers { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationDidBecomeActive:) + name:UIApplicationDidBecomeActiveNotification + object:[UIApplication sharedApplication]]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification + object:[UIApplication sharedApplication]]; +} + - (void)dealloc { // Remove all the notification observers registered. [[NSNotificationCenter defaultCenter] removeObserver:self]; diff --git a/FirebasePerformance/Tests/Unit/FPRTraceBackgroundActivityTrackerTest.m b/FirebasePerformance/Tests/Unit/FPRTraceBackgroundActivityTrackerTest.m index 272a7c418fa..7f86575f94b 100644 --- a/FirebasePerformance/Tests/Unit/FPRTraceBackgroundActivityTrackerTest.m +++ b/FirebasePerformance/Tests/Unit/FPRTraceBackgroundActivityTrackerTest.m @@ -61,4 +61,82 @@ - (void)testBackgroundTracking { }]; } +/** Tests that synchronous observer registration works correctly and observers are immediately + * available. */ +- (void)testObservers_synchronousRegistrationAddsObserver { + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init]; + XCTAssertNotNil(tracker); + + [notificationCenter postNotificationName:UIApplicationDidBecomeActiveNotification + object:[UIApplication sharedApplication]]; + XCTAssertEqual(tracker.traceBackgroundState, FPRTraceStateForegroundOnly); + + tracker = nil; + XCTAssertNil(tracker); + XCTAssertNoThrow([notificationCenter postNotificationName:UIApplicationDidBecomeActiveNotification + object:[UIApplication sharedApplication]]); + XCTAssertNoThrow([notificationCenter + postNotificationName:UIApplicationDidEnterBackgroundNotification + object:[UIApplication sharedApplication]]); +} + +/** Tests rapid creation and deallocation to verify race condition. */ +- (void)testRapidCreationAndDeallocation_noRaceCondition { + for (int i = 0; i < 100; i++) { + @autoreleasepool { + FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init]; + XCTAssertNotNil(tracker); + + [[NSNotificationCenter defaultCenter] + postNotificationName:UIApplicationDidBecomeActiveNotification + object:[UIApplication sharedApplication]]; + } + } + + XCTAssertNoThrow([[NSNotificationCenter defaultCenter] + postNotificationName:UIApplicationDidBecomeActiveNotification + object:[UIApplication sharedApplication]]); + XCTAssertNoThrow([[NSNotificationCenter defaultCenter] + postNotificationName:UIApplicationDidEnterBackgroundNotification + object:[UIApplication sharedApplication]]); +} + +/** Tests that observers are registered immediately after init on main thread. */ +- (void)testObservers_immediateRegistrationOnMainThread { + XCTAssertTrue([NSThread isMainThread]); + + FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init]; + + [[NSNotificationCenter defaultCenter] + postNotificationName:UIApplicationDidBecomeActiveNotification + object:[UIApplication sharedApplication]]; + + XCTAssertEqual(tracker.traceBackgroundState, FPRTraceStateForegroundOnly); +} + +/** Tests observer registration when created from background thread. */ +- (void)testObservers_registrationFromBackgroundThread { + XCTestExpectation *expectation = [self expectationWithDescription:@"Background thread creation"]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + FPRTraceBackgroundActivityTracker *tracker = [[FPRTraceBackgroundActivityTracker alloc] init]; + XCTAssertNotNil(tracker); + + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] + postNotificationName:UIApplicationDidBecomeActiveNotification + object:[UIApplication sharedApplication]]; + + XCTAssertEqual(tracker.traceBackgroundState, FPRTraceStateForegroundOnly); + [expectation fulfill]; + }); + }); + + [self waitForExpectationsWithTimeout:5.0 + handler:^(NSError *error) { + XCTAssertNil(error, @"Test timed out"); + }]; +} + @end