From c2e34f8f25a2785941905d4a57fe614d14cb7229 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Tue, 12 Aug 2025 15:30:13 -0700 Subject: [PATCH] fix: Race condition in SentryApplication and Swift implementation --- Sentry.xcodeproj/project.pbxproj | 42 ++- Sources/Sentry/SentryANRTrackingIntegration.m | 3 +- Sources/Sentry/SentryClient.m | 3 +- Sources/Sentry/SentryCrashIntegration.m | 1 - Sources/Sentry/SentryCrashWrapper.m | 4 +- Sources/Sentry/SentryDependencyContainer.m | 17 +- .../SentryDependencyContainerSwiftHelper.m | 3 +- Sources/Sentry/SentryNSApplication.m | 17 -- Sources/Sentry/SentrySDKInternal.m | 1 - .../Sentry/SentrySessionReplayIntegration.m | 9 +- Sources/Sentry/SentrySessionTracker.m | 3 +- Sources/Sentry/SentryTracer.m | 1 - Sources/Sentry/SentryUIApplication.m | 282 ------------------ .../Sentry/SentryUIViewControllerSwizzling.m | 1 - Sources/Sentry/SentryViewHierarchyProvider.m | 5 +- .../HybridPublic/SentryDependencyContainer.h | 3 +- Sources/Sentry/include/SentryApplication.h | 60 ---- Sources/Sentry/include/SentryNSApplication.h | 17 -- Sources/Sentry/include/SentryUIApplication.h | 33 -- .../Helper/SentryApplication+UIKit.swift | 152 ++++++++++ Sources/Swift/Helper/SentryApplication.swift | 45 +++ .../Swift/Helper/ThreadSafeApplication.swift | 49 +++ Tests/Perf/metrics-test.yml | 2 +- .../SentryDependencyContainerTests.swift | 7 +- .../SentryANRTrackingIntegrationTests.swift | 8 +- ...SentryUIViewControllerSwizzlingTests.swift | 2 +- .../Session/SentrySessionTrackerTests.swift | 48 +-- .../SentrySessionReplayIntegrationTests.swift | 31 +- Tests/SentryTests/SentryClientTests.swift | 54 ++-- Tests/SentryTests/SentryScreenShotTests.swift | 15 +- .../SentryTests/SentryTests-Bridging-Header.h | 4 - .../SentryTests/SentryUIApplication+Private.h | 21 -- .../SentryUIApplicationTests.swift | 74 +---- .../SentryViewHierarchyProviderTests.swift | 25 -- .../SentryTests/TestSentryNSApplication.swift | 13 + .../SentryTests/TestSentryUIApplication.swift | 61 ++++ 36 files changed, 420 insertions(+), 696 deletions(-) delete mode 100644 Sources/Sentry/SentryNSApplication.m delete mode 100644 Sources/Sentry/SentryUIApplication.m delete mode 100644 Sources/Sentry/include/SentryApplication.h delete mode 100644 Sources/Sentry/include/SentryNSApplication.h delete mode 100644 Sources/Sentry/include/SentryUIApplication.h create mode 100644 Sources/Swift/Helper/SentryApplication+UIKit.swift create mode 100644 Sources/Swift/Helper/SentryApplication.swift create mode 100644 Sources/Swift/Helper/ThreadSafeApplication.swift delete mode 100644 Tests/SentryTests/SentryUIApplication+Private.h create mode 100644 Tests/SentryTests/TestSentryNSApplication.swift create mode 100644 Tests/SentryTests/TestSentryUIApplication.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index f705d777d37..a57980161ba 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -803,9 +803,6 @@ A8F17B2E2901765900990B25 /* SentryRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B2D2901765900990B25 /* SentryRequest.m */; }; A8F17B342902870300990B25 /* SentryHttpStatusCodeRange.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */; }; D4009EB22D771BC20007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4009EB12D771BB90007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift */; }; - D40604472DD2471600C40DC0 /* SentryNSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = D40604462DD2471600C40DC0 /* SentryNSApplication.m */; }; - D40604492DD2472600C40DC0 /* SentryNSApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D40604482DD2472600C40DC0 /* SentryNSApplication.h */; }; - D406044B2DD2483D00C40DC0 /* SentryApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D406044A2DD2483D00C40DC0 /* SentryApplication.h */; }; D41415A72DEEE532003B14D5 /* SentryRedactViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41415A62DEEE532003B14D5 /* SentryRedactViewHelper.swift */; }; D4291A692DD61A3F00772088 /* SentryDispatchQueueProviderProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D4291A672DD61A3F00772088 /* SentryDispatchQueueProviderProtocol.h */; }; D4291A6D2DD62ACE00772088 /* SentryDispatchFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4291A6C2DD62AC800772088 /* SentryDispatchFactoryTests.m */; }; @@ -924,7 +921,6 @@ D855AD62286ED6A4002573E1 /* SentryCrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D855AD61286ED6A4002573E1 /* SentryCrashTests.m */; }; D855B3E827D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D855B3E727D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift */; }; D855B3EA27D652C700BCED76 /* TestCoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D855B3E927D652C700BCED76 /* TestCoreDataStack.swift */; }; - D85852BA27EDDC5900C6D8AE /* SentryUIApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = D85852B827EDDC5900C6D8AE /* SentryUIApplication.m */; }; D859696B27BECD8F0036A46E /* SentryCoreDataTrackingIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = D859696927BECD8F0036A46E /* SentryCoreDataTrackingIntegration.m */; }; D859696F27BECDA20036A46E /* SentryCoreDataTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = D859696D27BECDA20036A46E /* SentryCoreDataTracker.m */; }; D859697327BECDD20036A46E /* SentryCoreDataSwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = D859697127BECDD20036A46E /* SentryCoreDataSwizzling.m */; }; @@ -987,7 +983,6 @@ D8BFE37929A76666002E73F3 /* SentryTimeToDisplayTrackerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BFE37729A76519002E73F3 /* SentryTimeToDisplayTrackerTest.swift */; }; D8C66A362A77B1F70015696A /* SentryPropagationContext.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C66A342A77B1F70015696A /* SentryPropagationContext.h */; }; D8C66A372A77B1F70015696A /* SentryPropagationContext.m in Sources */ = {isa = PBXBuildFile; fileRef = D8C66A352A77B1F70015696A /* SentryPropagationContext.m */; }; - D8C67E9B28000E24007E326E /* SentryUIApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9928000E23007E326E /* SentryUIApplication.h */; }; D8CAC02E2BA0663E00E38F34 /* SentryReplayOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CAC02A2BA0663E00E38F34 /* SentryReplayOptions.swift */; }; D8CAC02F2BA0663E00E38F34 /* SentryVideoInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CAC02B2BA0663E00E38F34 /* SentryVideoInfo.swift */; }; D8CB74152947246600A5F964 /* SentryEnvelopeAttachmentHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = D8CB74142947246600A5F964 /* SentryEnvelopeAttachmentHeader.h */; }; @@ -1046,6 +1041,7 @@ F4FE9DFD2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */; }; F4FE9DFE2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */; }; FA034AC82DD3DB4900FE3107 /* SentryIntegrationProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FA034AC72DD3DB4900FE3107 /* SentryIntegrationProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FA1841832E4B457F005DEDC7 /* SentryApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA18417D2E4B457B005DEDC7 /* SentryApplication.swift */; }; FA21A2EF2E60E9CB00E7EADB /* EnvelopeComparison.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA21A2E92E60E9C700E7EADB /* EnvelopeComparison.swift */; }; FA21F0B42E4A2A80008B4E5A /* SentryAppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4C32972DF7513F001D7B01 /* SentryAppState.swift */; }; FA3734822E0EEA670091EF24 /* SentryScreenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3734812E0EEA670091EF24 /* SentryScreenshot.swift */; }; @@ -1059,6 +1055,9 @@ FA6555142E30181B009917BC /* SentrySDKInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6555132E30181B009917BC /* SentrySDKInternal.h */; }; FA6555162E30182B009917BC /* SentrySDKInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = FA6555152E30182B009917BC /* SentrySDKInternal.m */; }; FA65551A2E3018A3009917BC /* SentrySDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6555192E30189E009917BC /* SentrySDKTests.swift */; }; + FA66143A2E4B593900657755 /* SentryApplication+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6614392E4B593000657755 /* SentryApplication+UIKit.swift */; }; + FA6614FC2E4B8E1A00657755 /* TestSentryUIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6614FB2E4B8E1500657755 /* TestSentryUIApplication.swift */; }; + FA6615052E4BA4D700657755 /* ThreadSafeApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6615042E4BA4D200657755 /* ThreadSafeApplication.swift */; }; FA67DCC12DDBD4C800896B02 /* SentrySDKLog+Configure.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCC02DDBD4C800896B02 /* SentrySDKLog+Configure.swift */; }; FA67DCF52DDBD4EA00896B02 /* SentryCurrentDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCCA2DDBD4EA00896B02 /* SentryCurrentDateProvider.swift */; }; FA67DCF62DDBD4EA00896B02 /* SentryViewRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCE52DDBD4EA00896B02 /* SentryViewRenderer.swift */; }; @@ -1105,6 +1104,7 @@ FA90FAA82E06614E008CAAE8 /* SentryExtraPackages.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */; }; FA90FAFD2E070A3B008CAAE8 /* SentryURLRequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */; }; FAAB29F12E3D252300ACD577 /* SentrySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAB29F02E3D252000ACD577 /* SentrySession.swift */; }; + FAAB2EE02E4BE97500FE8B7E /* TestSentryNSApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAB2EDF2E4BE96F00FE8B7E /* TestSentryNSApplication.swift */; }; FAAB2F972E4D345800FE8B7E /* SentryUIDeviceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAB2F962E4D344F00FE8B7E /* SentryUIDeviceWrapper.swift */; }; FAAB30F32E4E8F2C00FE8B7E /* SentryInAppLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAB30F22E4E8F2C00FE8B7E /* SentryInAppLogic.swift */; }; FAB359982E05D7E90083D5E3 /* SentryEventSwiftHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = FAB359972E05D7E90083D5E3 /* SentryEventSwiftHelper.h */; }; @@ -2124,9 +2124,6 @@ A8F17B2D2901765900990B25 /* SentryRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryRequest.m; sourceTree = ""; }; A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryHttpStatusCodeRange.m; sourceTree = ""; }; D4009EB12D771BB90007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFileIOTrackerSwiftHelpersTests.swift; sourceTree = ""; }; - D40604462DD2471600C40DC0 /* SentryNSApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSApplication.m; sourceTree = ""; }; - D40604482DD2472600C40DC0 /* SentryNSApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSApplication.h; path = include/SentryNSApplication.h; sourceTree = ""; }; - D406044A2DD2483D00C40DC0 /* SentryApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryApplication.h; path = include/SentryApplication.h; sourceTree = ""; }; D41415A62DEEE532003B14D5 /* SentryRedactViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactViewHelper.swift; sourceTree = ""; }; D41909922D48FFF6002B83D0 /* SentryNSDictionarySanitize+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryNSDictionarySanitize+Tests.h"; sourceTree = ""; }; D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryNSDictionarySanitize+Tests.m"; sourceTree = ""; }; @@ -2258,7 +2255,6 @@ D855B3E727D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCoreDataTrackingIntegrationTest.swift; sourceTree = ""; }; D855B3E927D652C700BCED76 /* TestCoreDataStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCoreDataStack.swift; sourceTree = ""; }; D85790282976A69F00C6AC1F /* TestDebugImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDebugImageProvider.swift; sourceTree = ""; }; - D85852B827EDDC5900C6D8AE /* SentryUIApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryUIApplication.m; sourceTree = ""; }; D859696927BECD8F0036A46E /* SentryCoreDataTrackingIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCoreDataTrackingIntegration.m; sourceTree = ""; }; D859696D27BECDA20036A46E /* SentryCoreDataTracker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCoreDataTracker.m; sourceTree = ""; }; D859697127BECDD20036A46E /* SentryCoreDataSwizzling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCoreDataSwizzling.m; sourceTree = ""; }; @@ -2322,7 +2318,6 @@ D8BC28C72BFF5EBB0054DA4D /* SentryTouchTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTouchTracker.swift; sourceTree = ""; }; D8BC28CB2BFF78220054DA4D /* SentryRRWebTouchEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRRWebTouchEvent.swift; sourceTree = ""; }; D8BC28D42C00C6D30054DA4D /* StringExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionsTests.swift; sourceTree = ""; }; - D8BC83BA2AFCF08C00A662B7 /* SentryUIApplication+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryUIApplication+Private.h"; sourceTree = ""; }; D8BD2E27292D1F7300D96C6A /* SDK.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SDK.xcconfig; sourceTree = ""; }; D8BD2E67293619F600D96C6A /* PrivatesHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PrivatesHeader.h; path = include/HybridPublic/PrivatesHeader.h; sourceTree = ""; }; D8BFE37029A3782F002E73F3 /* SentryTimeToDisplayTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryTimeToDisplayTracker.h; path = include/SentryTimeToDisplayTracker.h; sourceTree = ""; }; @@ -2330,7 +2325,6 @@ D8BFE37729A76519002E73F3 /* SentryTimeToDisplayTrackerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTimeToDisplayTrackerTest.swift; sourceTree = ""; }; D8C66A342A77B1F70015696A /* SentryPropagationContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryPropagationContext.h; sourceTree = ""; }; D8C66A352A77B1F70015696A /* SentryPropagationContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryPropagationContext.m; sourceTree = ""; }; - D8C67E9928000E23007E326E /* SentryUIApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryUIApplication.h; path = include/SentryUIApplication.h; sourceTree = ""; }; D8CAC02A2BA0663E00E38F34 /* SentryReplayOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryReplayOptions.swift; sourceTree = ""; }; D8CAC02B2BA0663E00E38F34 /* SentryVideoInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryVideoInfo.swift; sourceTree = ""; }; D8CB74142947246600A5F964 /* SentryEnvelopeAttachmentHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryEnvelopeAttachmentHeader.h; path = include/SentryEnvelopeAttachmentHeader.h; sourceTree = ""; }; @@ -2392,6 +2386,7 @@ F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDefaultObjCRuntimeWrapper.swift; sourceTree = ""; }; F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryObjCRuntimeWrapper.swift; sourceTree = ""; }; FA034AC72DD3DB4900FE3107 /* SentryIntegrationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryIntegrationProtocol.h; path = Public/SentryIntegrationProtocol.h; sourceTree = ""; }; + FA18417D2E4B457B005DEDC7 /* SentryApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryApplication.swift; sourceTree = ""; }; FA21A2E92E60E9C700E7EADB /* EnvelopeComparison.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvelopeComparison.swift; sourceTree = ""; }; FA3734812E0EEA670091EF24 /* SentryScreenshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryScreenshot.swift; sourceTree = ""; }; FA3734832E0F07A20091EF24 /* SentryDependencyContainerSwiftHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDependencyContainerSwiftHelper.h; path = include/SentryDependencyContainerSwiftHelper.h; sourceTree = ""; }; @@ -2405,6 +2400,9 @@ FA6555132E30181B009917BC /* SentrySDKInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySDKInternal.h; path = include/SentrySDKInternal.h; sourceTree = ""; }; FA6555152E30182B009917BC /* SentrySDKInternal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySDKInternal.m; sourceTree = ""; }; FA6555192E30189E009917BC /* SentrySDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySDKTests.swift; sourceTree = ""; }; + FA6614392E4B593000657755 /* SentryApplication+UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryApplication+UIKit.swift"; sourceTree = ""; }; + FA6614FB2E4B8E1500657755 /* TestSentryUIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryUIApplication.swift; sourceTree = ""; }; + FA6615042E4BA4D200657755 /* ThreadSafeApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSafeApplication.swift; sourceTree = ""; }; FA67DCC02DDBD4C800896B02 /* SentrySDKLog+Configure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentrySDKLog+Configure.swift"; sourceTree = ""; }; FA67DCC22DDBD4EA00896B02 /* Locks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locks.swift; sourceTree = ""; }; FA67DCC32DDBD4EA00896B02 /* NumberExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberExtensions.swift; sourceTree = ""; }; @@ -2451,6 +2449,7 @@ FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryExtraPackages.swift; sourceTree = ""; }; FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryURLRequestFactory.swift; sourceTree = ""; }; FAAB29F02E3D252000ACD577 /* SentrySession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySession.swift; sourceTree = ""; }; + FAAB2EDF2E4BE96F00FE8B7E /* TestSentryNSApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSApplication.swift; sourceTree = ""; }; FAAB2F962E4D344F00FE8B7E /* SentryUIDeviceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUIDeviceWrapper.swift; sourceTree = ""; }; FAAB30F22E4E8F2C00FE8B7E /* SentryInAppLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryInAppLogic.swift; sourceTree = ""; }; FAB359972E05D7E90083D5E3 /* SentryEventSwiftHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryEventSwiftHelper.h; path = include/SentryEventSwiftHelper.h; sourceTree = ""; }; @@ -2606,6 +2605,9 @@ F48F767B2E60B555009D4E7D /* SentryNSTimerFactory.swift */, FABE8E162E307A7C0040809A /* Dependencies.swift */, FA6FC0A92E0B6B0E00ED2669 /* SentrySdkInfo.swift */, + FA18417D2E4B457B005DEDC7 /* SentryApplication.swift */, + FA6614392E4B593000657755 /* SentryApplication+UIKit.swift */, + FA6615042E4BA4D200657755 /* ThreadSafeApplication.swift */, FABE8E142E307A5C0040809A /* SentrySDK.swift */, FA3A42712E1C5F9B00A08C39 /* SentryNSNotificationCenterWrapper.swift */, FA6FC0A22E0B5AC800ED2669 /* SentrySdkPackage.swift */, @@ -2989,7 +2991,6 @@ D4F2B5332D0C69CC00649E42 /* Recording */, 62872B602BA1B84400A4FA7D /* Swift */, 7B3878E92490D90400EBDEA2 /* SentryClient+TestInit.h */, - D8BC83BA2AFCF08C00A662B7 /* SentryUIApplication+Private.h */, 84A305592BC9FD1600D84283 /* SentryTraceProfiler+Test.h */, 7B569DFE2590EEF600B653FC /* SentryScope+Equality.h */, 7B569E052590F04700B653FC /* SentryScope+Properties.h */, @@ -4134,11 +4135,6 @@ 8E25C94F25F836AB00DC215B /* Tools */ = { isa = PBXGroup; children = ( - D406044A2DD2483D00C40DC0 /* SentryApplication.h */, - D8C67E9928000E23007E326E /* SentryUIApplication.h */, - D85852B827EDDC5900C6D8AE /* SentryUIApplication.m */, - D40604482DD2472600C40DC0 /* SentryNSApplication.h */, - D40604462DD2471600C40DC0 /* SentryNSApplication.m */, D8F6A24A2885515B00320515 /* SentryPredicateDescriptor.h */, D8F6A2452885512100320515 /* SentryPredicateDescriptor.m */, 0A2D8DA6289BC905008720F6 /* SentryViewHierarchyProvider.h */, @@ -4431,6 +4427,8 @@ D81FDF0F280E9FEC0045E0E4 /* Tools */ = { isa = PBXGroup; children = ( + FA6614FB2E4B8E1500657755 /* TestSentryUIApplication.swift */, + FAAB2EDF2E4BE96F00FE8B7E /* TestSentryNSApplication.swift */, D43A2A132DD4815E00114724 /* SentryWeakMapTests.swift */, D4009EA02D77196F0007AF30 /* ViewCapture */, D81FDF10280EA0080045E0E4 /* SentryScreenShotTests.swift */, @@ -4931,13 +4929,11 @@ 925824C22CB5897700C9B20B /* SentrySessionReplayIntegration-Hybrid.h in Headers */, 7B0A54222521C21E00A71716 /* SentryFrameRemover.h in Headers */, 63FE70CD20DA4C1000CDBAE8 /* SentryCrashDoctor.h in Headers */, - D8C67E9B28000E24007E326E /* SentryUIApplication.h in Headers */, 7B6438AA26A70F24000D0F65 /* UIViewController+Sentry.h in Headers */, 33EB2A912C3412E4004FED3D /* SentryWithoutUIKit.h in Headers */, 639FCFAC1EBC811400778193 /* SentryUser.h in Headers */, F44858132E03579D0013E63B /* SentryCrashDynamicLinker+Test.h in Headers */, D8CB74192947285A00A5F964 /* SentryEnvelopeItemHeader.h in Headers */, - D40604492DD2472600C40DC0 /* SentryNSApplication.h in Headers */, 7D7F0A5F23DF3D2C00A4629C /* SentryGlobalEventProcessor.h in Headers */, D867063D27C3BC2400048851 /* SentryCoreDataTrackingIntegration.h in Headers */, 0A2D8D5D289815EB008720F6 /* SentryBaseIntegration.h in Headers */, @@ -5041,7 +5037,6 @@ 7BC9A20228F41350001E7C4C /* SentryMeasurementUnit.h in Headers */, 7B8ECBFA26498907005FE2EF /* SentryAppStateManager.h in Headers */, 639FCFA01EBC804600778193 /* SentryException.h in Headers */, - D406044B2DD2483D00C40DC0 /* SentryApplication.h in Headers */, 7BA235632600B61200E12865 /* SentryInternalNotificationNames.h in Headers */, 7BAF3DB9243C9777008A5414 /* SentryTransport.h in Headers */, 845C16D52A622A5B00EC9519 /* SentryTracer+Private.h in Headers */, @@ -5762,6 +5757,7 @@ FABE8E152E307A5E0040809A /* SentrySDK.swift in Sources */, FA67DCFF2DDBD4EA00896B02 /* SentryMXManager.swift in Sources */, D41415A72DEEE532003B14D5 /* SentryRedactViewHelper.swift in Sources */, + FA66143A2E4B593900657755 /* SentryApplication+UIKit.swift in Sources */, FA67DD002DDBD4EA00896B02 /* SentryMaskRenderer.swift in Sources */, FA67DD012DDBD4EA00896B02 /* SentryMXCallStackTree.swift in Sources */, FAAB29F12E3D252300ACD577 /* SentrySession.swift in Sources */, @@ -5796,6 +5792,7 @@ D8ACE3C72762187200F5A213 /* SentryNSDataSwizzling.m in Sources */, F48F767C2E60B555009D4E7D /* SentryNSTimerFactory.swift in Sources */, 638DC9A11EBC6B6400A66E41 /* SentryRequestOperation.m in Sources */, + FA6615052E4BA4D700657755 /* ThreadSafeApplication.swift in Sources */, 63AA767A1EB8D20500D153DE /* SentryLogC.m in Sources */, 84B0DFF42CD2CF64007FB332 /* SentryUserFeedbackFormController.swift in Sources */, 6344DDBA1EC3115C00D9160D /* SentryCrashReportConverter.m in Sources */, @@ -5826,6 +5823,7 @@ 849B8F9C2C6E906900148E1F /* SentryUserFeedbackThemeConfiguration.swift in Sources */, F452437E2DE60B71003E8F50 /* SentryUseNSExceptionCallstackWrapper.m in Sources */, 63FE70D320DA4C1000CDBAE8 /* SentryCrashMonitor_AppState.c in Sources */, + FA1841832E4B457F005DEDC7 /* SentryApplication.swift in Sources */, 849B8F9D2C6E906900148E1F /* SentryUserFeedbackWidgetConfiguration.swift in Sources */, 620467AC2D3FFD230025F06C /* SentryNSErrorCodable.swift in Sources */, 639FCFA51EBC809A00778193 /* SentryStacktrace.m in Sources */, @@ -5891,7 +5889,6 @@ D8A3649C2C91AA3300AC569B /* SentryReplayApi.m in Sources */, 7B42C48227E08F4B009B58C2 /* SentryDependencyContainer.m in Sources */, 639FCFAD1EBC811400778193 /* SentryUser.m in Sources */, - D40604472DD2471600C40DC0 /* SentryNSApplication.m in Sources */, 7DAC589123D8B2E0001CF26B /* SentryGlobalEventProcessor.m in Sources */, 7BBD189E244EC8D200427C76 /* SentryRetryAfterHeaderParser.m in Sources */, F41362152E1C568400B84443 /* SentryScopePersistentStore+Context.swift in Sources */, @@ -5931,7 +5928,6 @@ 63FE711520DA4C1000CDBAE8 /* SentryCrashJSONCodec.c in Sources */, 03F84D3327DD4191008FE43F /* SentryMachLogging.cpp in Sources */, 92235CAC2E15369900865983 /* SentryLogBatcher.swift in Sources */, - D85852BA27EDDC5900C6D8AE /* SentryUIApplication.m in Sources */, FAB3599A2E05D8080083D5E3 /* SentryEventSwiftHelper.m in Sources */, F41362112E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift in Sources */, 7B4E375F258231FC00059C93 /* SentryAttachment.m in Sources */, @@ -6144,6 +6140,7 @@ 8EAE8E5E2681768000D6958B /* URLSessionTaskMock.m in Sources */, D8CE69BC277E39C700C6EC5C /* SentryFileIOTrackingIntegrationObjCTests.m in Sources */, D85D3BEA278DF63D001B2889 /* SentryByteCountFormatterTests.swift in Sources */, + FAAB2EE02E4BE97500FE8B7E /* TestSentryNSApplication.swift in Sources */, 7BBD18A2244EE2FD00427C76 /* TestResponseFactory.swift in Sources */, 628B89022D841D7F004B6F2A /* SentryDateUtilsTests.swift in Sources */, D808FB8B281BCE96009A2A33 /* TestSentrySwizzleWrapper.swift in Sources */, @@ -6232,6 +6229,7 @@ 62E2119A2DAE99FC007D7262 /* SentryAsyncSafeLog.m in Sources */, 84EB21962BF01CEA00EDDA28 /* SentryCrashInstallationTests.swift in Sources */, 7BFE7A0A27A1B6B000D2B66E /* SentryWatchdogTerminationTrackingIntegrationTests.swift in Sources */, + FA6614FC2E4B8E1A00657755 /* TestSentryUIApplication.swift in Sources */, D8292D7D2A39A027009872F7 /* UrlSanitizedTests.swift in Sources */, 621D22012DBB7E09006F9C48 /* SentryANRTrackerV1IntegrationTests.swift in Sources */, 7BA61CAF247BBF3C00C130A8 /* SentryDebugImageProviderTests.swift in Sources */, diff --git a/Sources/Sentry/SentryANRTrackingIntegration.m b/Sources/Sentry/SentryANRTrackingIntegration.m index 602d31bb09e..01260757ef5 100644 --- a/Sources/Sentry/SentryANRTrackingIntegration.m +++ b/Sources/Sentry/SentryANRTrackingIntegration.m @@ -17,7 +17,6 @@ #import "SentryThread.h" #import "SentryThreadInspector.h" #import "SentryThreadWrapper.h" -#import "SentryUIApplication.h" #import #import @@ -120,7 +119,7 @@ - (void)anrDetectedWithType:(enum SentryANRType)type // If the app is not active, the main thread may be blocked or too busy. // Since there is no UI for the user to interact, there is no need to report app hang. - if (SentryDependencyContainer.sharedInstance.application.applicationState + if (SentryDependencyContainer.sharedInstance.threadsafeApplication.applicationState != UIApplicationStateActive) { return; } diff --git a/Sources/Sentry/SentryClient.m b/Sources/Sentry/SentryClient.m index 2264097725f..b1fbf910c5f 100644 --- a/Sources/Sentry/SentryClient.m +++ b/Sources/Sentry/SentryClient.m @@ -42,7 +42,6 @@ #import "SentryTransport.h" #import "SentryTransportAdapter.h" #import "SentryTransportFactory.h" -#import "SentryUIApplication.h" #import "SentryUseNSExceptionCallstackWrapper.h" #import "SentryUser.h" #import "SentryWatchdogTerminationTracker.h" @@ -785,7 +784,7 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event context[@"app"] = app; UIApplicationState appState = - [SentryDependencyContainer sharedInstance].application.applicationState; + [SentryDependencyContainer sharedInstance].threadsafeApplication.applicationState; BOOL inForeground = appState == UIApplicationStateActive; app[@"in_foreground"] = @(inForeground); event.context = context; diff --git a/Sources/Sentry/SentryCrashIntegration.m b/Sources/Sentry/SentryCrashIntegration.m index 217756885fd..8b2603a03a6 100644 --- a/Sources/Sentry/SentryCrashIntegration.m +++ b/Sources/Sentry/SentryCrashIntegration.m @@ -25,7 +25,6 @@ #import #if SENTRY_HAS_UIKIT -# import "SentryUIApplication.h" # import #endif diff --git a/Sources/Sentry/SentryCrashWrapper.m b/Sources/Sentry/SentryCrashWrapper.m index 64d24b60481..27fca40612c 100644 --- a/Sources/Sentry/SentryCrashWrapper.m +++ b/Sources/Sentry/SentryCrashWrapper.m @@ -14,7 +14,6 @@ #include #if SENTRY_HAS_UIKIT -# import "SentryUIApplication.h" # import #endif @@ -184,7 +183,8 @@ - (void)enrichScope:(SentryScope *)scope // The UIWindowScene is unavailable on visionOS #if SENTRY_TARGET_REPLAY_SUPPORTED - NSArray *appWindows = SentryDependencyContainer.sharedInstance.application.windows; + NSArray *appWindows = + [SentryDependencyContainer.sharedInstance.application getWindows]; if ([appWindows count] > 0) { UIScreen *appScreen = appWindows.firstObject.screen; if (appScreen != nil) { diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index 6a2680c6757..cc9dfe579b7 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -1,6 +1,5 @@ #import "SentryANRTrackerV1.h" -#import "SentryApplication.h" #import "SentryDispatchFactory.h" #import "SentryDisplayLinkWrapper.h" #import "SentryExtraContextProvider.h" @@ -38,15 +37,10 @@ #if SENTRY_HAS_UIKIT # import "SentryANRTrackerV2.h" # import "SentryFramesTracker.h" -# import "SentryUIApplication.h" # import # import #endif // SENTRY_HAS_UIKIT -#if TARGET_OS_OSX -# import "SentryNSApplication.h" -#endif - #if !TARGET_OS_WATCH # import "SentryReachability.h" #endif // !TARGET_OS_WATCH @@ -156,15 +150,16 @@ - (instancetype)init _binaryImageCache = [[SentryBinaryImageCache alloc] init]; _dateProvider = SentryDependencies.dateProvider; - _notificationCenterWrapper = [NSNotificationCenter defaultCenter]; + _notificationCenterWrapper = NSNotificationCenter.defaultCenter; #if SENTRY_HAS_UIKIT _uiDeviceWrapper = [[SentryDefaultUIDeviceWrapper alloc] initWithQueueWrapper:_dispatchQueueWrapper]; - _application = [[SentryUIApplication alloc] - initWithNotificationCenterWrapper:_notificationCenterWrapper - dispatchQueueWrapper:_dispatchQueueWrapper]; + _application = UIApplication.sharedApplication; + _threadsafeApplication = [[SentryThreadsafeApplication alloc] + initWithInitialState:_application.unsafeApplicationState + notificationCenter:_notificationCenterWrapper]; #elif TARGET_OS_OSX - _application = [[SentryNSApplication alloc] init]; + _application = NSApplication.sharedApplication; #endif // SENTRY_HAS_UIKIT _processInfoWrapper = NSProcessInfo.processInfo; diff --git a/Sources/Sentry/SentryDependencyContainerSwiftHelper.m b/Sources/Sentry/SentryDependencyContainerSwiftHelper.m index 8a6edaddd8d..adac37f1fff 100644 --- a/Sources/Sentry/SentryDependencyContainerSwiftHelper.m +++ b/Sources/Sentry/SentryDependencyContainerSwiftHelper.m @@ -2,7 +2,6 @@ #import "SentryDependencyContainer.h" #import "SentrySDK+Private.h" #import "SentrySwift.h" -#import "SentryUIApplication.h" @implementation SentryDependencyContainerSwiftHelper @@ -10,7 +9,7 @@ @implementation SentryDependencyContainerSwiftHelper + (NSArray *)windows { - return SentryDependencyContainer.sharedInstance.application.windows; + return [SentryDependencyContainer.sharedInstance.application getWindows]; } #endif // SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/SentryNSApplication.m b/Sources/Sentry/SentryNSApplication.m deleted file mode 100644 index 1a2b52c3a12..00000000000 --- a/Sources/Sentry/SentryNSApplication.m +++ /dev/null @@ -1,17 +0,0 @@ -#import "SentryNSApplication.h" - -#if TARGET_OS_OSX - -# import - -@implementation SentryNSApplication - -- (BOOL)isActive -{ - NSApplication *application = [NSApplication sharedApplication]; - return application.isActive; -} - -@end - -#endif // TARGET_OS_OSX diff --git a/Sources/Sentry/SentrySDKInternal.m b/Sources/Sentry/SentrySDKInternal.m index 71cb50c161a..1cdd82b8a92 100644 --- a/Sources/Sentry/SentrySDKInternal.m +++ b/Sources/Sentry/SentrySDKInternal.m @@ -23,7 +23,6 @@ #import "SentrySerialization.h" #import "SentrySwift.h" #import "SentryTransactionContext.h" -#import "SentryUIApplication.h" #import "SentryUseNSExceptionCallstackWrapper.h" #import "SentryUserFeedbackIntegration.h" diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 4c6b9c78096..0522ef12445 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -22,7 +22,6 @@ # import "SentrySessionReplaySyncC.h" # import "SentrySwift.h" # import "SentrySwizzle.h" -# import "SentryUIApplication.h" # import NS_ASSUME_NONNULL_BEGIN @@ -331,7 +330,7 @@ - (void)startSession - (void)runReplayForAvailableWindow { - if (SentryDependencyContainer.sharedInstance.application.windows.count > 0) { + if ([SentryDependencyContainer.sharedInstance.application getWindows].count > 0) { SENTRY_LOG_DEBUG(@"[Session Replay] Running replay for available window"); // If a window its already available start replay right away [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; @@ -414,7 +413,8 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions displayLinkWrapper:displayLinkWrapper]; [self.sessionReplay - startWithRootView:SentryDependencyContainer.sharedInstance.application.windows.firstObject + startWithRootView:[SentryDependencyContainer.sharedInstance.application getWindows] + .firstObject fullSession:shouldReplayFullSession]; [_notificationCenter addObserver:self @@ -770,7 +770,8 @@ - (void)showMaskPreview:(CGFloat)opacity return; } - UIWindow *window = SentryDependencyContainer.sharedInstance.application.windows.firstObject; + UIWindow *window = + [SentryDependencyContainer.sharedInstance.application getWindows].firstObject; if (window == nil) { SENTRY_LOG_WARN(@"[Session Replay] No UIWindow available to display preview"); return; diff --git a/Sources/Sentry/SentrySessionTracker.m b/Sources/Sentry/SentrySessionTracker.m index b82a925fb00..c49155285e8 100644 --- a/Sources/Sentry/SentrySessionTracker.m +++ b/Sources/Sentry/SentrySessionTracker.m @@ -1,5 +1,4 @@ #import "SentrySessionTracker.h" -#import "SentryApplication.h" #import "SentryClient+Private.h" #import "SentryClient.h" #import "SentryFileManager.h" @@ -94,7 +93,7 @@ - (void)start // Edge case: When starting the SDK after the app did become active, we need to call // didBecomeActive manually to start the session. This is the case when // closing the SDK and starting it again. - if (self.application.isActive) { + if (self.application.mainThread_isActive) { [self startSession]; } #else diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index 1d93c12630b..03cf957d7b3 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -27,7 +27,6 @@ #import "SentryTracer+Private.h" #import "SentryTransaction.h" #import "SentryTransactionContext.h" -#import "SentryUIApplication.h" #import #if SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryUIApplication.m b/Sources/Sentry/SentryUIApplication.m deleted file mode 100644 index 5d22ad620ba..00000000000 --- a/Sources/Sentry/SentryUIApplication.m +++ /dev/null @@ -1,282 +0,0 @@ -#import "SentryUIApplication.h" -#import "SentryInternalDefines.h" -#import "SentryLogC.h" -#import "SentrySwift.h" - -#if SENTRY_HAS_UIKIT - -# import - -@interface SentryUIApplication () - -@property (nonatomic, assign) UIApplicationState appState; -@property (nonatomic, strong) id notificationCenterWrapper; -@property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueueWrapper; - -@end - -@implementation SentryUIApplication - -- (instancetype)initWithNotificationCenterWrapper: - (id)notificationCenterWrapper - dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper -{ - if (self = [super init]) { - self.notificationCenterWrapper = notificationCenterWrapper; - self.dispatchQueueWrapper = dispatchQueueWrapper; - - [self.notificationCenterWrapper addObserver:self - selector:@selector(didEnterBackground) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; - - [self.notificationCenterWrapper addObserver:self - selector:@selector(didBecomeActive) - name:UIApplicationDidBecomeActiveNotification - object:nil]; - - // We store the application state when the app is initialized - // and we keep track of its changes by the notifications - // this way we avoid calling sharedApplication in a background thread - [self.dispatchQueueWrapper - dispatchAsyncOnMainQueue:^{ self.appState = self.sharedApplication.applicationState; }]; - } - return self; -} - -- (void)dealloc -{ - [self.notificationCenterWrapper removeObserver:self name:nil object:nil]; -} - -- (UIApplication *)sharedApplication -{ - if (![UIApplication respondsToSelector:@selector(sharedApplication)]) - return nil; - - return [UIApplication performSelector:@selector(sharedApplication)]; -} - -- (nullable id)getApplicationDelegate:(UIApplication *)application -{ - return application.delegate; -} - -- (NSArray *)getApplicationConnectedScenes:(UIApplication *)application - API_AVAILABLE(ios(13.0), tvos(13.0)) -{ - if (application && [application respondsToSelector:@selector(connectedScenes)]) { - return [application.connectedScenes allObjects]; - } - - return @[]; -} - -- (NSArray *)windows -{ - __block NSArray *windows = nil; - [_dispatchQueueWrapper - dispatchSyncOnMainQueue:^{ - UIApplication *app = [self sharedApplication]; - NSMutableSet *result = [NSMutableSet set]; - - if (@available(iOS 13.0, tvOS 13.0, *)) { - NSArray *scenes = [self getApplicationConnectedScenes:app]; - for (UIScene *scene in scenes) { - if (scene.activationState == UISceneActivationStateForegroundActive - && scene.delegate && - [scene.delegate respondsToSelector:@selector(window)]) { - id window = [scene.delegate performSelector:@selector(window)]; - if (window) { - [result addObject:window]; - } - } - } - } - - id appDelegate = [self getApplicationDelegate:app]; - - if ([appDelegate respondsToSelector:@selector(window)] && appDelegate.window != nil) { - [result addObject:SENTRY_UNWRAP_NULLABLE(UIWindow, appDelegate.window)]; - } - - windows = [result allObjects]; - } - timeout:0.01]; - return windows ?: @[]; -} - -- (NSArray *)relevantViewControllers -{ - NSArray *windows = [self windows]; - if ([windows count] == 0) { - return nil; - } - - NSMutableArray *result = [NSMutableArray array]; - - for (UIWindow *window in windows) { - NSArray *vcs = [self relevantViewControllerFromWindow:window]; - if (vcs != nil) { - [result addObjectsFromArray:vcs]; - } - } - - return result; -} - -- (nullable NSArray *)relevantViewControllersNames -{ - __block NSArray *result = nil; - __weak SentryUIApplication *weakSelf = self; - - [_dispatchQueueWrapper - dispatchSyncOnMainQueue:^{ - if (weakSelf == nil) { - SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not doing anything."); - return; - } - - NSArray *viewControllers = weakSelf.relevantViewControllers; - NSMutableArray *vcsNames = - [[NSMutableArray alloc] initWithCapacity:viewControllers.count]; - for (UIViewController *vc in viewControllers) { - [vcsNames addObject:[SwiftDescriptor getViewControllerClassName:vc]]; - } - result = [NSArray arrayWithArray:vcsNames]; - } - timeout:0.01]; - - return result; -} - -- (NSArray *)relevantViewControllerFromWindow:(UIWindow *)window -{ - UIViewController *rootViewController = window.rootViewController; - if (rootViewController == nil) { - return nil; - } - - NSMutableArray *result = - [NSMutableArray arrayWithObject:rootViewController]; - NSUInteger index = 0; - - while (index < result.count) { - UIViewController *topVC = result[index]; - // If the view controller is presenting another one, usually in a modal form. - if (topVC.presentedViewController != nil) { - UIViewController *_Nonnull topPresentationVC - = SENTRY_UNWRAP_NULLABLE(UIViewController, topVC.presentedViewController); - if ([topPresentationVC isKindOfClass:UIAlertController.class]) { - // If the view controller being presented is an Alert, we know that - // we reached the end of the view controller stack and the presenter is - // the top view controller. - break; - } - - [result replaceObjectAtIndex:index withObject:topPresentationVC]; - - continue; - } - - // The top view controller is meant for navigation and not content - if ([self isContainerViewController:topVC]) { - NSArray *contentViewController = - [self relevantViewControllerFromContainer:topVC]; - if (contentViewController != nil && contentViewController.count > 0) { - [result removeObjectAtIndex:index]; - [result addObjectsFromArray:contentViewController]; - } else { - break; - } - continue; - } - - UIViewController *relevantChild = nil; - for (UIViewController *childVC in topVC.childViewControllers) { - // Sometimes a view controller is used as container for a navigation controller - // If the navigation is occupying the whole view controller we will consider this the - // case. - if ([self isContainerViewController:childVC] && childVC.isViewLoaded - && CGRectEqualToRect(childVC.view.frame, topVC.view.bounds)) { - relevantChild = childVC; - break; - } - } - - if (relevantChild != nil) { - [result replaceObjectAtIndex:index withObject:relevantChild]; - continue; - } - - index++; - } - - return result; -} - -- (BOOL)isContainerViewController:(UIViewController *)viewController -{ - return [viewController isKindOfClass:UINavigationController.class] || - [viewController isKindOfClass:UITabBarController.class] || - [viewController isKindOfClass:UISplitViewController.class] || - [viewController isKindOfClass:UIPageViewController.class]; -} - -- (nullable NSArray *)relevantViewControllerFromContainer: - (UIViewController *)containerVC -{ - if ([containerVC isKindOfClass:UINavigationController.class]) { - UIViewController *_Nullable containerTopVC = - [(UINavigationController *)containerVC topViewController]; - if (containerTopVC) { - return @[ SENTRY_UNWRAP_NULLABLE(UIViewController, containerTopVC) ]; - } - } - if ([containerVC isKindOfClass:UITabBarController.class]) { - UITabBarController *tbController = (UITabBarController *)containerVC; - NSInteger selectedIndex = tbController.selectedIndex; - if (tbController.viewControllers.count > selectedIndex) { - return @[ SENTRY_UNWRAP_NULLABLE( - UIViewController, [tbController.viewControllers objectAtIndex:selectedIndex]) ]; - } - } - if ([containerVC isKindOfClass:UISplitViewController.class]) { - UISplitViewController *splitVC = (UISplitViewController *)containerVC; - if (splitVC.viewControllers.count > 0) { - return [splitVC viewControllers]; - } - } - if ([containerVC isKindOfClass:UIPageViewController.class]) { - UIPageViewController *pageVC = (UIPageViewController *)containerVC; - if (pageVC.viewControllers.count > 0) { - return @[ SENTRY_UNWRAP_NULLABLE( - UIViewController, [pageVC.viewControllers objectAtIndex:0]) ]; - } - } - return nil; -} - -- (UIApplicationState)applicationState -{ - return self.appState; -} - -- (void)didEnterBackground -{ - self.appState = UIApplicationStateBackground; -} - -- (void)didBecomeActive -{ - self.appState = UIApplicationStateActive; -} - -- (BOOL)isActive -{ - return self.appState == UIApplicationStateActive; -} - -@end - -#endif // SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/SentryUIViewControllerSwizzling.m b/Sources/Sentry/SentryUIViewControllerSwizzling.m index 6cd58a3b54f..6759f4eb0de 100644 --- a/Sources/Sentry/SentryUIViewControllerSwizzling.m +++ b/Sources/Sentry/SentryUIViewControllerSwizzling.m @@ -10,7 +10,6 @@ # import "SentrySwizzle.h" # import "SentryUIViewControllerPerformanceTracker.h" # import -# import # import # import # import diff --git a/Sources/Sentry/SentryViewHierarchyProvider.m b/Sources/Sentry/SentryViewHierarchyProvider.m index f8e92eea5db..da5f35aa027 100644 --- a/Sources/Sentry/SentryViewHierarchyProvider.m +++ b/Sources/Sentry/SentryViewHierarchyProvider.m @@ -6,7 +6,6 @@ # import "SentryCrashJSONCodec.h" # import "SentryLogC.h" # import "SentrySwift.h" -# import "SentryUIApplication.h" # import static int @@ -47,7 +46,7 @@ - (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispa - (BOOL)saveViewHierarchy:(NSString *)filePath { - NSArray *windows = [self.sentryUIApplication windows]; + NSArray *windows = [self.sentryUIApplication getWindows]; const char *path = [filePath UTF8String]; int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); @@ -80,7 +79,7 @@ - (NSData *)appViewHierarchyFromMainThread - (NSData *)appViewHierarchy { NSMutableData *result = [[NSMutableData alloc] init]; - NSArray *windows = [self.sentryUIApplication windows]; + NSArray *windows = [self.sentryUIApplication getWindows]; if (![self processViewHierarchy:windows addFunction:writeJSONDataToMemory diff --git a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h index e2d9f4d6e8e..c744f9ce62d 100644 --- a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h +++ b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h @@ -16,6 +16,7 @@ @class SentryNSTimerFactory; @class SentrySwizzleWrapper; @class SentrySysctl; +@class SentryThreadsafeApplication; @class SentrySystemWrapper; @class SentryThreadWrapper; @class SentryThreadInspector; @@ -42,7 +43,6 @@ #if SENTRY_UIKIT_AVAILABLE @class SentryFramesTracker; @class SentryScreenshot; -@class SentryUIApplication; @class SentryViewHierarchyProvider; @class SentryUIViewControllerPerformanceTracker; @class SentryWatchdogTerminationScopeObserver; @@ -94,6 +94,7 @@ SENTRY_NO_INIT @property (nonatomic, strong) SentrySysctl *sysctlWrapper; @property (nonatomic, strong) id rateLimits; @property (nonatomic, strong) id application; +@property (nonatomic, strong) SentryThreadsafeApplication *threadsafeApplication; #if SENTRY_HAS_REACHABILITY @property (nonatomic, strong) SentryReachability *reachability; diff --git a/Sources/Sentry/include/SentryApplication.h b/Sources/Sentry/include/SentryApplication.h deleted file mode 100644 index cea1bac4f56..00000000000 --- a/Sources/Sentry/include/SentryApplication.h +++ /dev/null @@ -1,60 +0,0 @@ -#import "SentryDefines.h" -#import - -#if SENTRY_HAS_UIKIT -@class UIApplication; -@class UIScene; -@class UIWindow; -@class UIViewController; -@protocol UIApplicationDelegate; - -# import -#endif - -typedef NS_ENUM(NSInteger, UIApplicationState); - -NS_ASSUME_NONNULL_BEGIN - -/** - * Protocol used to provide cross-platform access to the application. - */ -@protocol SentryApplication - -// MARK: - Shared methods - -- (BOOL)isActive; - -// MARK: - UIKit-specific methods - -#if SENTRY_HAS_UIKIT -/** - * Returns the application state available at @c UIApplication.sharedApplication.applicationState - */ -@property (nonatomic, readonly) UIApplicationState applicationState; - -/** - * All windows connected to scenes. - */ -@property (nonatomic, readonly, nullable) NSArray *windows; - -/** - * Retrieves the application delegate for given UIApplication - */ -- (nullable id)getApplicationDelegate:(UIApplication *)application; - -/** - * Retrieves connected scenes for given UIApplication - */ -- (NSArray *)getApplicationConnectedScenes:(UIApplication *)application - API_AVAILABLE(ios(13.0), tvos(13.0)); - -/** - * Use @c [SentryUIApplication relevantViewControllers] and convert the - * result to a string array with the class name of each view controller. - */ -- (nullable NSArray *)relevantViewControllersNames; -#endif // SENTRY_HAS_UIKIT - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryNSApplication.h b/Sources/Sentry/include/SentryNSApplication.h deleted file mode 100644 index f9f7546b8b1..00000000000 --- a/Sources/Sentry/include/SentryNSApplication.h +++ /dev/null @@ -1,17 +0,0 @@ -#import "SentryApplication.h" -#import "SentryDefines.h" - -#if TARGET_OS_OSX - -NS_ASSUME_NONNULL_BEGIN - -/** - * A helper tool to retrieve informations from the application instance. - */ -@interface SentryNSApplication : NSObject - -@end - -NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_OSX diff --git a/Sources/Sentry/include/SentryUIApplication.h b/Sources/Sentry/include/SentryUIApplication.h deleted file mode 100644 index 01a35cfca6b..00000000000 --- a/Sources/Sentry/include/SentryUIApplication.h +++ /dev/null @@ -1,33 +0,0 @@ -#import "SentryApplication.h" -#import "SentryDefines.h" - -#if SENTRY_HAS_UIKIT - -@class UIApplication; -@class UIScene; -@class UIWindow; -@class UIViewController; -@class SentryDispatchQueueWrapper; -@protocol SentryNSNotificationCenterWrapper; -@protocol UIApplicationDelegate; - -typedef NS_ENUM(NSInteger, UIApplicationState); - -NS_ASSUME_NONNULL_BEGIN - -/** - * A helper tool to retrieve informations from the application instance. - */ -@interface SentryUIApplication : NSObject -SENTRY_NO_INIT - -- (instancetype)initWithNotificationCenterWrapper: - (id)notificationCenterWrapper - dispatchQueueWrapper: - (SentryDispatchQueueWrapper *)dispatchQueueWrapper; - -@end - -NS_ASSUME_NONNULL_END - -#endif // SENTRY_HAS_UIKIT diff --git a/Sources/Swift/Helper/SentryApplication+UIKit.swift b/Sources/Swift/Helper/SentryApplication+UIKit.swift new file mode 100644 index 00000000000..bfcf24b806b --- /dev/null +++ b/Sources/Swift/Helper/SentryApplication+UIKit.swift @@ -0,0 +1,152 @@ +#if !os(macOS) && !os(watchOS) && !SENTRY_NO_UIKIT +import UIKit + +@objc @_spi(Private) extension UIApplication: SentryApplication { + + @objc public func getWindows() -> [UIWindow]? { + internal_getWindows() + } + + @objc public func relevantViewControllersNames() -> [String]? { + internal_relevantViewControllersNames() + } + + @objc public var unsafeApplicationState: State { + applicationState + } + + @objc public var mainThread_isActive: Bool { + unsafeApplicationState == .active + } +} + +extension SentryApplication { + // This cannot be declared with @objc so until we delete more ObjC code it needs a separate + // function than the objc visible one. + public func internal_getWindows() -> [UIWindow]? { + var windows = Set() + Dependencies.dispatchQueueWrapper.dispatchSyncOnMainQueue({ [weak self] in + guard let self else { return } + if #available(iOS 13.0, tvOS 13.0, *) { + let scenes = connectedScenes + for scene in scenes { + if scene.activationState == .foregroundActive { + if + let delegate = scene.delegate as? UIWindowSceneDelegate, + let window = delegate.window { + if let window { + windows.insert(window) + } + } + } + } + } + + if let window = delegate?.window { + if let window { + windows.insert(window) + } + } + }, timeout: 0.01) + return Array(windows) + } + + // This cannot be declared with @objc so until we delete more ObjC code it needs a separate + // function than the objc visible one. + public func internal_relevantViewControllersNames() -> [String]? { + var result: [String]? + Dependencies.dispatchQueueWrapper.dispatchSyncOnMainQueue({ [weak self] in + guard let self else { return } + let viewControllers = self.relevantviewControllers() ?? [] + result = viewControllers.map { SwiftDescriptor.getViewControllerClassName($0) } + }, timeout: 0.01) + return result + } + + private func relevantviewControllers() -> [UIViewController]? { + let windows = getWindows() + guard !(windows?.isEmpty ?? true) else { return nil } + + return windows?.compactMap { relevantViewControllerFromWindow($0) }.flatMap { $0 } + } + + private func relevantViewControllerFromWindow(_ window: UIWindow) -> [UIViewController]? { + let viewController = window.rootViewController + guard let viewController else { return nil } + + var result = [UIViewController]() + result.append(viewController) + var index = 0 + while index < result.count { + let topVC = result[index] + // If the view controller is presenting another one, usually in a modal form. + if let presented = topVC.presentedViewController { + if presented is UIAlertController { + break + } + result[index] = presented + continue + } + + // The top view controller is meant for navigation and not content + if isContainerViewController(topVC) { + if let contentViewController = relevantViewControllerFromContainer(topVC), contentViewController.count > 0 { + result.remove(at: index) + result.append(contentsOf: contentViewController) + } else { + break + } + continue + } + + var relevantChild: UIViewController? + for childVC in topVC.children { + // Sometimes a view controller is used as container for a navigation controller + // If the navigation is occupying the whole view controller we will consider this the + // case. + if isContainerViewController(childVC), childVC.isViewLoaded, childVC.view.frame == topVC.view.bounds { + relevantChild = childVC + break + } + } + if let relevantChild { + result[index] = relevantChild + } + + index += 1 + } + return result + } + + func relevantViewControllerFromContainer(_ vc: UIViewController) -> [UIViewController]? { + if let navigationController = vc as? UINavigationController { + return navigationController.topViewController.map { [$0] } + } + if let tabBarController = vc as? UITabBarController { + let selectedIndex = tabBarController.selectedIndex + if let vcs = tabBarController.viewControllers, vcs.count > selectedIndex { + return [vcs[selectedIndex]] + } else { + return nil + } + } + if let splitViewController = vc as? UISplitViewController { + if splitViewController.viewControllers.count > 0 { + return splitViewController.viewControllers + } + } + + if let pageViewController = vc as? UIPageViewController { + if let vcs = pageViewController.viewControllers, vcs.count > 0 { + return [vcs[0]] + } + } + + return nil + } + + func isContainerViewController(_ vc: UIViewController) -> Bool { + return vc is UINavigationController || vc is UITabBarController || vc is UISplitViewController || vc is UIPageViewController + } +} +#endif diff --git a/Sources/Swift/Helper/SentryApplication.swift b/Sources/Swift/Helper/SentryApplication.swift new file mode 100644 index 00000000000..327eceebc3c --- /dev/null +++ b/Sources/Swift/Helper/SentryApplication.swift @@ -0,0 +1,45 @@ +#if canImport(AppKit) +import AppKit +#endif +#if canImport(UIKit) && !SENTRY_NO_UIKIT +import UIKit +#endif + +@objc @_spi(Private) public protocol SentryApplication { + + // This can only be accessed on the main thread + var mainThread_isActive: Bool { get } + + #if !os(macOS) && !os(watchOS) && !SENTRY_NO_UIKIT + + /** + * Returns the application state available at @c UIApplication.sharedApplication.applicationState + * Must be called on the main thread. + */ + var unsafeApplicationState: UIApplication.State { get } + +/** + * All windows connected to scenes. + */ + func getWindows() -> [UIWindow]? + + @available(iOS 13.0, tvOS 13.0, *) + var connectedScenes: Set { get } + + var delegate: UIApplicationDelegate? { get } + +/** + * Use @c [SentryUIApplication relevantViewControllers] and convert the + * result to a string array with the class name of each view controller. + */ + func relevantViewControllersNames() -> [String]? + #endif // canImport(UIKit) && !SENTRY_NO_UIKIT +} + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +@objc @_spi(Private) extension NSApplication: SentryApplication { + public var mainThread_isActive: Bool { + isActive + } +} +#endif diff --git a/Sources/Swift/Helper/ThreadSafeApplication.swift b/Sources/Swift/Helper/ThreadSafeApplication.swift new file mode 100644 index 00000000000..1e8bee59b4b --- /dev/null +++ b/Sources/Swift/Helper/ThreadSafeApplication.swift @@ -0,0 +1,49 @@ +#if !os(macOS) && !os(watchOS) && !SENTRY_NO_UIKIT +import UIKit + +@objc @_spi(Private) public final class SentryThreadsafeApplication: NSObject { + private let notificationCenter: SentryNSNotificationCenterWrapper + + @objc public init(initialState: UIApplication.State, notificationCenter: SentryNSNotificationCenterWrapper) { + self.notificationCenter = notificationCenter + _internalState = initialState + super.init() + + notificationCenter.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) + notificationCenter.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) + } + + deinit { + notificationCenter.removeObserver(self, name: nil, object: nil) + } + + private let lock = NSRecursiveLock() + private var _internalState: UIApplication.State + @objc public var applicationState: UIApplication.State { + var state: UIApplication.State + lock.lock() + state = _internalState + lock.unlock() + return state + } + + @objc + public var isActive: Bool { + return applicationState == .active + } + + @objc + private func didEnterBackground() { + lock.lock() + _internalState = .background + lock.unlock() + } + + @objc + private func didBecomeActive() { + lock.lock() + _internalState = .active + lock.unlock() + } +} +#endif diff --git a/Tests/Perf/metrics-test.yml b/Tests/Perf/metrics-test.yml index 8122c413d69..80b01845f4a 100644 --- a/Tests/Perf/metrics-test.yml +++ b/Tests/Perf/metrics-test.yml @@ -11,4 +11,4 @@ startupTimeTest: binarySizeTest: diffMin: 200 KiB - diffMax: 925 KiB + diffMax: 935 KiB diff --git a/Tests/SentryTests/Helper/SentryDependencyContainerTests.swift b/Tests/SentryTests/Helper/SentryDependencyContainerTests.swift index 5f5ba796e03..6ad0e7de980 100644 --- a/Tests/SentryTests/Helper/SentryDependencyContainerTests.swift +++ b/Tests/SentryTests/Helper/SentryDependencyContainerTests.swift @@ -108,7 +108,6 @@ final class SentryDependencyContainerTests: XCTestCase { #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) XCTAssertNotNil(SentryDependencyContainer.sharedInstance().uiDeviceWrapper) - XCTAssertNotNil(SentryDependencyContainer.sharedInstance().application) #endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) // Lazy Dependencies @@ -326,12 +325,18 @@ final class SentryDependencyContainerTests: XCTestCase { SentrySDKInternal.setStart(with: options) let container = SentryDependencyContainer.sharedInstance() + #if canImport(UIKit) + container.application = TestSentryUIApplication() + #else + container.application = TestSentryNSApplication() + #endif // -- Act -- let tracker = container.getSessionTracker(with: options) // -- Assert -- // Verify that the tracker uses the dependencies from the container + XCTAssertIdentical(Dynamic(tracker).application.asAnyObject, container.application) XCTAssertIdentical(Dynamic(tracker).dateProvider.asAnyObject, container.dateProvider) XCTAssertIdentical(Dynamic(tracker).notificationCenter.asAnyObject, container.notificationCenterWrapper) diff --git a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift index 6a282702e4b..c4c51c59a6a 100644 --- a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift @@ -324,13 +324,11 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) func testANRDetected_ButBackground_EventNotCaptured() { - class BackgroundSentryUIApplication: SentryUIApplication { - override var applicationState: UIApplication.State { .background } - } - givenInitializedTracker() setUpThreadInspector() - SentryDependencyContainer.sharedInstance().application = BackgroundSentryUIApplication(notificationCenterWrapper: TestNSNotificationCenterWrapper(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) + let backgroundUIApplication = TestSentryUIApplication() + backgroundUIApplication.unsafeApplicationState = .background + SentryDependencyContainer.sharedInstance().application = backgroundUIApplication Dynamic(sut).anrDetectedWithType(SentryANRType.unknown) diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerSwizzlingTests.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerSwizzlingTests.swift index 1012629d4b2..434e98ec698 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerSwizzlingTests.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerSwizzlingTests.swift @@ -326,7 +326,7 @@ class SentryUIViewControllerSwizzlingTests: XCTestCase { } } -class MockApplication: NSObject, SentryUIApplicationProtocol { +class MockApplication: NSObject, SentryUIApplication { class MockApplicationDelegate: NSObject, UIApplicationDelegate { var window: UIWindow? diff --git a/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift b/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift index d3f08124c56..fb2ae25135b 100644 --- a/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift @@ -35,7 +35,7 @@ class SentrySessionTrackerTests: XCTestCase { #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) application = TestSentryUIApplication() - application.applicationState = .inactive + application.unsafeApplicationState = .inactive SentryDependencyContainer.sharedInstance().application = application #else application = TestSentryNSApplication() @@ -539,7 +539,7 @@ class SentrySessionTrackerTests: XCTestCase { // become the active app yet. // // This can be observed by viewing the application state in `UIAppDelegate.didFinishLaunchingWithOptions`. - fixture.application.applicationState = .inactive + fixture.application.unsafeApplicationState = .inactive #else // The Sentry SDK should be initialized in the `NSApplicationDelegate.applicationDidFinishLaunching` // At this point the app is not active yet. @@ -564,7 +564,7 @@ class SentrySessionTrackerTests: XCTestCase { #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) // When the app stops, the app state is `inactive`. // This can be observed by viewing the application state in `UIAppDelegate.applicationDidEnterBackground`. - fixture.application.applicationState = .inactive + fixture.application.unsafeApplicationState = .inactive #else // When the app crashes, the app state is `inactive`. // @@ -580,7 +580,7 @@ class SentrySessionTrackerTests: XCTestCase { // When the app stops, the app state is `inactive`. // // This can be observed by viewing the application state in `UIAppDelegate.applicationDidEnterBackground`. - fixture.application.applicationState = .inactive + fixture.application.unsafeApplicationState = .inactive #else // When the app crashes, the app state is `inactive`. // @@ -599,7 +599,7 @@ class SentrySessionTrackerTests: XCTestCase { #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) // When the app becomes active, the app state is `active`. // This can be observed by viewing the application state in `UIAppDelegate.applicationDidBecomeActive`. - fixture.application.applicationState = .active + fixture.application.unsafeApplicationState = .active #else // When the app becomes active, the app state is `active`. // This can be observed by viewing the application state in `NSApplicationDelegate.applicationDidBecomeActive`. @@ -620,7 +620,7 @@ class SentrySessionTrackerTests: XCTestCase { willResignActive() #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) // It is expected that the app state is background when the didEnterBackground is called - fixture.application.applicationState = .background + fixture.application.unsafeApplicationState = .background fixture.notificationCenter .post( Notification( @@ -642,7 +642,7 @@ class SentrySessionTrackerTests: XCTestCase { // When the app is about to resign being active, it is still active. // // This can be observed by viewing the application state in `UIAppDelegate.applicationWillResignActive`. - fixture.application.applicationState = .active + fixture.application.unsafeApplicationState = .active #else // When the app becomes active, the app state is `active`. // @@ -662,7 +662,7 @@ class SentrySessionTrackerTests: XCTestCase { private func hybridSdkDidBecomeActive() { // When an app did become active, it is in the active state. #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) - fixture.application.applicationState = .active + fixture.application.unsafeApplicationState = .active #else fixture.application.setIsActive(true) #endif @@ -687,7 +687,7 @@ class SentrySessionTrackerTests: XCTestCase { // When terminating an app, it will first move to the background and then terminate. // // This can be observed by viewing the application state in `UIAppDelegate.applicationWillTerminate`. - fixture.application.applicationState = .background + fixture.application.unsafeApplicationState = .background #else // When terminating an app, it will first move to the background and then terminate. // @@ -878,7 +878,7 @@ class SentrySessionTrackerTests: XCTestCase { private func postHybridSdkDidBecomeActiveNotification() { // When the hybrid SDK posts this notification, the app should be in active state #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) - fixture.application.applicationState = .active + fixture.application.unsafeApplicationState = .active #else fixture.application.setIsActive(true) #endif @@ -891,32 +891,4 @@ class SentrySessionTrackerTests: XCTestCase { ) ) } - -#if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) - private class TestSentryUIApplication: SentryUIApplication { - init() { - super.init(notificationCenterWrapper: TestNSNotificationCenterWrapper(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) - } - - private var _underlyingAppState: UIApplication.State = .active - override var applicationState: UIApplication.State { - get { _underlyingAppState } - set { _underlyingAppState = newValue } - } - - override func isActive() -> Bool { - return applicationState == .active - } - } -#else - private class TestSentryNSApplication: SentryNSApplication { - private var _underlyingIsActive = true - func setIsActive(_ isActive: Bool) { - _underlyingIsActive = isActive - } - override func isActive() -> Bool { - return _underlyingIsActive - } - } -#endif } diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index 2eec6b45ad3..b294bd744ea 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -8,24 +8,6 @@ import XCTest @available(*, deprecated, message: "This is deprecated because SentryOptions integrations is deprecated") class SentrySessionReplayIntegrationTests: XCTestCase { - private class TestSentryUIApplication: SentryUIApplication { - init() { - super.init(notificationCenterWrapper: TestNSNotificationCenterWrapper(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) - } - - var windowsMock: [UIWindow]? = [UIWindow()] - var screenName: String? - - override var windows: [UIWindow]? { - windowsMock - } - - override func relevantViewControllersNames() -> [String]? { - guard let screenName = screenName else { return nil } - return [screenName] - } - } - private class TestCrashWrapper: SentryCrashWrapper { let traced: Bool @@ -49,6 +31,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { private var globalEventProcessor = SentryGlobalEventProcessor() override func setUp() { + uiApplication.windows = [UIWindow()] SentryDependencyContainer.sharedInstance().application = uiApplication SentryDependencyContainer.sharedInstance().reachability = TestSentryReachability() SentryDependencyContainer.sharedInstance().globalEventProcessor = globalEventProcessor @@ -133,13 +116,13 @@ class SentrySessionReplayIntegrationTests: XCTestCase { } func testWaitForNotificationWithNoWindow() throws { - uiApplication.windowsMock = nil + uiApplication.windows = nil startSDK(sessionSampleRate: 1, errorSampleRate: 0) let sut = try getSut() XCTAssertNil(sut.sessionReplay) - uiApplication.windowsMock = [UIWindow()] + uiApplication.windows = [UIWindow()] NotificationCenter.default.post(name: UIScene.didActivateNotification, object: nil) XCTAssertNotNil(sut.sessionReplay) } @@ -199,7 +182,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { func testScreenNameFromSentryUIApplication() throws { startSDK(sessionSampleRate: 1, errorSampleRate: 1) let sut: SentrySessionReplayDelegate = try getSut() as! SentrySessionReplayDelegate - uiApplication.screenName = "Test Screen" + uiApplication._relevantViewControllerNames = ["Test Screen"] XCTAssertEqual(sut.currentScreenNameForSessionReplay(), "Test Screen") } @@ -211,7 +194,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { } let sut: SentrySessionReplayDelegate = try getSut() as! SentrySessionReplayDelegate - uiApplication.screenName = "Test Screen" + uiApplication._relevantViewControllerNames = ["Test Screen"] XCTAssertEqual(sut.currentScreenNameForSessionReplay(), "Scope Screen") } @@ -612,7 +595,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { func testShowMaskPreviewForDebug() throws { SentryDependencyContainer.sharedInstance().crashWrapper = TestCrashWrapper(traced: true) let window = UIWindow() - uiApplication.windowsMock = [window] + uiApplication.windows = [window] startSDK(sessionSampleRate: 0, errorSampleRate: 1) let sut = try getSut() @@ -625,7 +608,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { func testDontShowMaskPreviewForRelese() throws { SentryDependencyContainer.sharedInstance().crashWrapper = TestCrashWrapper(traced: false) let window = UIWindow() - uiApplication.windowsMock = [window] + uiApplication.windows = [window] startSDK(sessionSampleRate: 0, errorSampleRate: 1) let sut = try getSut() diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index b612fd8b8e8..273fb48e42e 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -302,7 +302,9 @@ class SentryClientTests: XCTestCase { #if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) func testCaptureEventWithCurrentScreen() throws { - SentryDependencyContainer.sharedInstance().application = TestSentryUIApplication() + let testApplication = TestSentryUIApplication() + SentryDependencyContainer.sharedInstance().application = testApplication + testApplication._relevantViewControllerNames = ["ClientTestViewController"] let event = Event() event.exceptions = [ Exception(value: "", type: "")] @@ -315,7 +317,9 @@ class SentryClientTests: XCTestCase { } func testCaptureEventWithCurrentScreenInTheScope() throws { - SentryDependencyContainer.sharedInstance().application = TestSentryUIApplication() + let testApplication = TestSentryUIApplication() + SentryDependencyContainer.sharedInstance().application = testApplication + testApplication._relevantViewControllerNames = ["ClientTestViewController"] let event = Event() event.exceptions = [ Exception(value: "", type: "")] @@ -359,7 +363,9 @@ class SentryClientTests: XCTestCase { // swiftlint:enable avoid_dispatch_groups_in_tests func testCaptureTransactionWithScreen() throws { - SentryDependencyContainer.sharedInstance().application = TestSentryUIApplication() + let testApplication = TestSentryUIApplication() + SentryDependencyContainer.sharedInstance().application = testApplication + testApplication._relevantViewControllerNames = ["ClientTestViewController"] let tracer = SentryTracer(transactionContext: TransactionContext(operation: "Operation"), hub: nil) let event = try XCTUnwrap(Dynamic(tracer).toTransaction() as Transaction?) fixture.getSut().capture(event: event, scope: fixture.scope) @@ -397,7 +403,9 @@ class SentryClientTests: XCTestCase { } func testCaptureTransactionWithoutScreen() throws { - SentryDependencyContainer.sharedInstance().application = TestSentryUIApplication() + let testApplication = TestSentryUIApplication() + SentryDependencyContainer.sharedInstance().application = testApplication + testApplication._relevantViewControllerNames = ["ClientTestViewController"] let event = Transaction(trace: SentryTracer(context: SpanContext(operation: "test"), framesTracker: nil), children: []) fixture.getSut().capture(event: event, scope: fixture.scope) @@ -1052,7 +1060,7 @@ class SentryClientTests: XCTestCase { #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) func testCaptureExceptionWithAppStateInForegroudWhenAppIsInForeground() throws { let app = TestSentryUIApplication() - app.applicationState = .active + app.unsafeApplicationState = .active SentryDependencyContainer.sharedInstance().application = app let event = TestData.event @@ -1064,7 +1072,7 @@ class SentryClientTests: XCTestCase { func testCaptureTransaction_WithAppStateInForegroudWhenAppIsInForeground() throws { let app = TestSentryUIApplication() - app.applicationState = .active + app.unsafeApplicationState = .active SentryDependencyContainer.sharedInstance().application = app let event = fixture.transaction @@ -1075,9 +1083,7 @@ class SentryClientTests: XCTestCase { } func testCaptureExceptionWithAppStateInForegroudWhenAppIsInBackground() throws { - let app = TestSentryUIApplication() - app.applicationState = .background - SentryDependencyContainer.sharedInstance().application = app + SentryDependencyContainer.sharedInstance().threadsafeApplication = SentryThreadsafeApplication(initialState: .background, notificationCenter: NotificationCenter.default) let event = TestData.event fixture.getSut().capture(event: event) @@ -1087,9 +1093,7 @@ class SentryClientTests: XCTestCase { } func testCaptureExceptionWithAppStateInForegroudWhenAppIsInactive() throws { - let app = TestSentryUIApplication() - app.applicationState = .inactive - SentryDependencyContainer.sharedInstance().application = app + SentryDependencyContainer.sharedInstance().threadsafeApplication = SentryThreadsafeApplication(initialState: .inactive, notificationCenter: NotificationCenter.default) let event = TestData.event fixture.getSut().capture(event: event) @@ -1099,9 +1103,7 @@ class SentryClientTests: XCTestCase { } func testCaptureExceptionWithAppStateInForegroundDoNotOverwriteExistingValue() throws { - let app = TestSentryUIApplication() - app.applicationState = .active - SentryDependencyContainer.sharedInstance().application = app + SentryDependencyContainer.sharedInstance().threadsafeApplication = SentryThreadsafeApplication(initialState: .active, notificationCenter: NotificationCenter.default) let event = TestData.event event.context?["app"] = ["in_foreground": "keep-value"] @@ -2421,28 +2423,6 @@ private extension SentryClientTests { } } -#if os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) - class TestSentryUIApplication: SentryUIApplication { - init() { - super.init(notificationCenterWrapper: TestNSNotificationCenterWrapper(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) - } - - override func relevantViewControllers() -> [UIViewController] { - return [ClientTestViewController()] - } - - private var _underlyingAppState: UIApplication.State = .active - override var applicationState: UIApplication.State { - get { _underlyingAppState } - set { _underlyingAppState = newValue } - } - } - - class ClientTestViewController: UIViewController { - - } -#endif - func assertSampleRate( sampleRate: NSNumber?, randomValue: Double, isSampled: Bool) throws { fixture.random.value = randomValue diff --git a/Tests/SentryTests/SentryScreenShotTests.swift b/Tests/SentryTests/SentryScreenShotTests.swift index b4c86e9810d..63cbfcd660c 100644 --- a/Tests/SentryTests/SentryScreenShotTests.swift +++ b/Tests/SentryTests/SentryScreenShotTests.swift @@ -6,7 +6,7 @@ import XCTest class SentryScreenShotTests: XCTestCase { private class Fixture { - let uiApplication = TestSentryUIApplication(notificationCenterWrapper: TestNSNotificationCenterWrapper(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) + let uiApplication = TestSentryUIApplication() var sut: SentryScreenshot { return SentryScreenshot() @@ -108,19 +108,6 @@ class SentryScreenShotTests: XCTestCase { XCTAssertEqual(0, data.count, "No screenshot should be taken, cause the image has zero height.") } - - private class TestSentryUIApplication: SentryUIApplication { - private var _windows: [UIWindow]? - - override var windows: [UIWindow]? { - get { - return _windows - } - set { - _windows = newValue - } - } - } private class TestWindow: UIWindow { var onDrawHierarchy: (() -> Void)? diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 54b7753d798..5c601605ec7 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -14,8 +14,6 @@ # import "SentryFramesTracker+TestInit.h" # import "SentrySessionReplayIntegration+Private.h" # import "SentrySessionReplayIntegration+Test.h" -# import "SentryUIApplication+Private.h" -# import "SentryUIApplication.h" # import "SentryUIEventTracker.h" # import "SentryUIEventTrackerTransactionMode.h" # import "SentryUIEventTrackingIntegration.h" @@ -40,8 +38,6 @@ # import "SentryTraceProfiler+Test.h" #endif // SENTRY_TARGET_PROFILING_SUPPORTED -#import "SentryNSApplication.h" - #import "NSLocale+Sentry.h" #import "NSMutableDictionary+Sentry.h" #import "PrivateSentrySDKOnly.h" diff --git a/Tests/SentryTests/SentryUIApplication+Private.h b/Tests/SentryTests/SentryUIApplication+Private.h deleted file mode 100644 index ab5de62e05c..00000000000 --- a/Tests/SentryTests/SentryUIApplication+Private.h +++ /dev/null @@ -1,21 +0,0 @@ - -#ifndef SentryUIApplication_Private_h -#define SentryUIApplication_Private_h - -#import "SentryDefines.h" -#import "SentryUIApplication.h" - -#if SENTRY_HAS_UIKIT - -NS_ASSUME_NONNULL_BEGIN - -@interface SentryUIApplication () - -- (NSArray *)relevantViewControllers; - -@end - -NS_ASSUME_NONNULL_END - -#endif /* SentryUIApplication_Private_h */ -#endif diff --git a/Tests/SentryTests/SentryUIApplicationTests.swift b/Tests/SentryTests/SentryUIApplicationTests.swift index 06184dbff70..db956280b79 100644 --- a/Tests/SentryTests/SentryUIApplicationTests.swift +++ b/Tests/SentryTests/SentryUIApplicationTests.swift @@ -1,3 +1,4 @@ +@_spi(Private) @testable import Sentry @_spi(Private) import SentryTestUtils import XCTest @@ -10,17 +11,17 @@ class SentryUIApplicationTests: XCTestCase { } func test_noScene_delegateWithNoWindow() { - let sut = MockSentryUIApplicationTests() - XCTAssertEqual(sut.windows?.count, 0) + let sut = TestSentryUIApplication() + XCTAssertEqual(sut.getWindows()?.count, 0) } func test_delegateWithWindow() { - let sut = MockSentryUIApplicationTests() + let sut = TestSentryUIApplication() let delegate = TestApplicationDelegate() sut.appDelegate = delegate sut.appDelegate?.window = UIWindow() - XCTAssertEqual(sut.windows?.count, 1) + XCTAssertEqual(sut.getWindows()?.count, 1) } //Somehow this is running under iOS 12 and is breaking the test. Disabling it. @@ -32,10 +33,10 @@ class SentryUIApplicationTests: XCTestCase { let scene1 = MockUIScene() scene1.delegate = sceneDelegate - let sut = MockSentryUIApplicationTests() + let sut = TestSentryUIApplication() sut.scenes = [scene1] - XCTAssertEqual(sut.windows?.count, 1) + XCTAssertEqual(sut.getWindows()?.count, 1) } //Somehow this is running under iOS 12 and is breaking the test. Disabling it. @@ -49,11 +50,11 @@ class SentryUIApplicationTests: XCTestCase { let delegate = TestApplicationDelegate() delegate.window = UIWindow() - let sut = MockSentryUIApplicationTests() + let sut = TestSentryUIApplication() sut.scenes = [scene1] sut.appDelegate = delegate - XCTAssertEqual(sut.windows?.count, 2) + XCTAssertEqual(sut.getWindows()?.count, 2) } //Somehow this is running under iOS 12 and is breaking the test. Disabling it. @@ -68,11 +69,11 @@ class SentryUIApplicationTests: XCTestCase { let delegate = TestApplicationDelegate() delegate.window = window - let sut = MockSentryUIApplicationTests() + let sut = TestSentryUIApplication() sut.scenes = [scene1] sut.appDelegate = delegate - XCTAssertEqual(sut.windows?.count, 1) + XCTAssertEqual(sut.getWindows()?.count, 1) } //Somehow this is running under iOS 12 and is breaking the test. Disabling it. @@ -83,63 +84,14 @@ class SentryUIApplicationTests: XCTestCase { let scene1 = MockUIScene() scene1.delegate = sceneDelegate - let sut = MockSentryUIApplicationTests() + let sut = TestSentryUIApplication() sut.scenes = [scene1] - XCTAssertEqual(sut.windows?.count, 0) - } - - @available(iOS 13.0, tvOS 13.0, *) - func test_ApplicationState() { - let sut = MockSentryUIApplicationTests() - sut.notificationCenterWrapper.ignoreRemoveObserver = true - XCTAssertEqual(sut.applicationState, .active) - - sut.notificationCenterWrapper.addObserverWithObjectInvocations.invocations.forEach { (observer: WeakReference, selector: Selector, name: NSNotification.Name?, _: Any?) in - if name == UIApplication.didEnterBackgroundNotification { - sut.perform(selector, with: observer) - } - } - - XCTAssertEqual(sut.applicationState, .background) - - sut.notificationCenterWrapper.addObserverWithObjectInvocations.invocations.forEach { (observer: WeakReference, selector: Selector, name: NSNotification.Name?, _: Any?) in - if name == UIApplication.didBecomeActiveNotification { - sut.perform(selector, with: observer) - } - } - - XCTAssertEqual(sut.applicationState, .active) - } - - private class TestApplicationDelegate: NSObject, UIApplicationDelegate { - var window: UIWindow? + XCTAssertEqual(sut.getWindows()?.count, 0) } private class TestUISceneDelegate: NSObject, UIWindowSceneDelegate { var window: UIWindow? } - - private class MockSentryUIApplicationTests: SentryUIApplication { - - let notificationCenterWrapper: TestNSNotificationCenterWrapper - - weak var appDelegate: TestApplicationDelegate? - var scenes: [Any]? - - init() { - notificationCenterWrapper = TestNSNotificationCenterWrapper() - super.init(notificationCenterWrapper: notificationCenterWrapper, dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) - } - - override func getDelegate(_ application: UIApplication) -> UIApplicationDelegate? { - return appDelegate - } - - @available(iOS 13.0, tvOS 13.0, *) - override func getConnectedScenes(_ application: UIApplication) -> [UIScene] { - return scenes as? [UIScene] ?? super.getConnectedScenes(application) - } - } } #endif diff --git a/Tests/SentryTests/SentryViewHierarchyProviderTests.swift b/Tests/SentryTests/SentryViewHierarchyProviderTests.swift index 3e5a45fbb0f..37a15d6430f 100644 --- a/Tests/SentryTests/SentryViewHierarchyProviderTests.swift +++ b/Tests/SentryTests/SentryViewHierarchyProviderTests.swift @@ -214,30 +214,5 @@ class SentryViewHierarchyProviderTests: XCTestCase { wait(for: [ex], timeout: 5) XCTAssertTrue(fixture.uiApplication.calledOnMainThread, "appViewHierarchy is not using the main thread to get UI windows") } - - private class TestSentryUIApplication: SentryUIApplication { - - init() { - super.init(notificationCenterWrapper: TestNSNotificationCenterWrapper(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) - } - - private var _windows: [UIWindow]? - private var _calledOnMainThread = true - - var calledOnMainThread: Bool { - return _calledOnMainThread - } - - override var windows: [UIWindow]? { - get { - _calledOnMainThread = Thread.isMainThread - return _windows - } - set { - _calledOnMainThread = Thread.isMainThread - _windows = newValue - } - } - } } #endif diff --git a/Tests/SentryTests/TestSentryNSApplication.swift b/Tests/SentryTests/TestSentryNSApplication.swift new file mode 100644 index 00000000000..acf4e0ba5cf --- /dev/null +++ b/Tests/SentryTests/TestSentryNSApplication.swift @@ -0,0 +1,13 @@ +@_spi(Private) @testable import Sentry + +#if !(os(iOS) || targetEnvironment(macCatalyst) || os(tvOS)) +class TestSentryNSApplication: SentryApplication { + private var _underlyingIsActive = true + func setIsActive(_ isActive: Bool) { + _underlyingIsActive = isActive + } + var mainThread_isActive: Bool { + return _underlyingIsActive + } +} +#endif diff --git a/Tests/SentryTests/TestSentryUIApplication.swift b/Tests/SentryTests/TestSentryUIApplication.swift new file mode 100644 index 00000000000..2e3cb31453c --- /dev/null +++ b/Tests/SentryTests/TestSentryUIApplication.swift @@ -0,0 +1,61 @@ +#if canImport(UIKit) +@_spi(Private) @testable import Sentry + +final class TestSentryUIApplication: SentryApplication { + func getWindows() -> [UIWindow]? { + if let windows { + return windows + } + return internal_getWindows() + } + + private var _windows: [UIWindow]? + private(set) var calledOnMainThread = true + var windows: [UIWindow]? { + get { + calledOnMainThread = Thread.isMainThread + return _windows + } + set { + calledOnMainThread = Thread.isMainThread + _windows = newValue + } + } + + var _relevantViewControllerNames: [String]? + func relevantViewControllersNames() -> [String]? { + if let _relevantViewControllerNames { + return _relevantViewControllerNames + } + return UIApplication.shared.relevantViewControllersNames() + } + + private var _underlyingAppState: UIApplication.State = .active + var unsafeApplicationState: UIApplication.State { + get { _underlyingAppState } + set { _underlyingAppState = newValue } + } + + var mainThread_isActive: Bool { + return unsafeApplicationState == .active + } + + var connectedScenes: Set { + if let scenes = scenes as? [UIScene] { + return Set(scenes) + } + return [] + } + + weak var appDelegate: TestApplicationDelegate? + var scenes: [Any]? + + var delegate: UIApplicationDelegate? { + return appDelegate + } +} + +final class TestApplicationDelegate: NSObject, UIApplicationDelegate { + var window: UIWindow? +} +#endif