From 8462dc242e5530e03496ea4581873b1cbfd1a8ff Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Tue, 31 Oct 2023 15:22:59 +0000 Subject: [PATCH 1/6] RUM-1978 Initial fix --- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 2 +- .../Scopes/Utils/RUMViewIdentity.swift | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 8ff7c85a41..dd4aa875b1 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -221,7 +221,7 @@ extension Monitor: RUMMonitorProtocol { process( command: RUMStartViewCommand( time: dateProvider.now, - identity: viewController, + identity: viewController.asRUMViewIdentity(), name: name, path: nil, attributes: attributes diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift index da756501cb..dc368faa1f 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift @@ -66,7 +66,7 @@ extension String: RUMViewIdentifiable { // MARK: - `RUMViewIdentity` /// Manages the `RUMViewIdentifiable` by using either reference or value semantic. -internal struct RUMViewIdentity { +internal struct RUMViewIdentity: RUMViewIdentifiable { private weak var object: AnyObject? private let value: Any? @@ -104,4 +104,20 @@ internal struct RUMViewIdentity { var identifiable: RUMViewIdentifiable? { return (object as? RUMViewIdentifiable) ?? (value as? RUMViewIdentifiable) } + + // MARK: - RUMViewIdentifiable + + func asRUMViewIdentity() -> RUMViewIdentity { + self + } + + var defaultViewPath: String { + if let selfObject = object as? RUMViewIdentifiable { + return selfObject.defaultViewPath + } else if let selfValue = value as? RUMViewIdentifiable { + return selfValue.defaultViewPath + } else { + return "" + } + } } From 60ed92b0b2906e86dc745329d2b1bf360de7aa7c Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Tue, 31 Oct 2023 17:39:47 +0000 Subject: [PATCH 2/6] RUM-1978 Refactor to RUMViewIdentity --- Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift | 3 +- .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 11 +- .../Tests/Datadog/RUM/RUMDebuggingTests.swift | 6 +- .../Views/RUMViewsHandler.swift | 19 +-- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 6 +- .../Sources/RUMMonitor/RUMCommand.swift | 6 +- .../RUMMonitor/Scopes/RUMSessionScope.swift | 13 +- .../RUMMonitor/Scopes/RUMViewScope.swift | 4 +- .../Scopes/Utils/RUMViewIdentity.swift | 36 ++-- .../Views/RUMViewsHandlerTests.swift | 12 +- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 11 +- .../Scopes/RUMApplicationScopeTests.swift | 14 +- .../Scopes/RUMSessionScopeTests.swift | 22 +-- .../Scopes/RUMUserActionScopeTests.swift | 24 +-- .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 160 +++++++++--------- .../Scopes/Utils/RUMViewIdentityTests.swift | 8 +- 16 files changed, 172 insertions(+), 183 deletions(-) diff --git a/Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift b/Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift index b34897b0db..7048182a20 100644 --- a/Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift +++ b/Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift @@ -14,7 +14,8 @@ class RUMMonitorE2ETests: E2ETests { let actionTypePool = [RUMActionType.swipe, .scroll, .tap, .custom] let nonCustomActionTypePool = [RUMActionType.swipe, .scroll, .tap] - /// - api-surface: RUMMonitorProtocol.startView(key: String,name: String? = nil,attributes: [AttributeKey: AttributeValue] = [:]) + /// - api-surface: RUMMonitorProtocol.startView(key: String, +ame: String? = nil,attributes: [AttributeKey: AttributeValue] = [:]) /// /// - data monitor: /// ```rum diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index 841d2a4ecc..60d07507ce 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -168,7 +168,7 @@ extension RUMStartViewCommand: AnyMockable, RandomMockable { return .mockWith( time: .mockRandomInThePast(), attributes: mockRandomAttributes(), - identity: String.mockRandom(), + identity: String.mockRandom().asRUMViewIdentity(), name: .mockRandom(), path: .mockRandom() ) @@ -177,7 +177,7 @@ extension RUMStartViewCommand: AnyMockable, RandomMockable { static func mockWith( time: Date = Date(), attributes: [AttributeKey: AttributeValue] = [:], - identity: RUMViewIdentifiable = mockView, + identity: RUMViewIdentity = mockViewIdentity, name: String = .mockAny(), path: String? = nil ) -> RUMStartViewCommand { @@ -198,14 +198,14 @@ extension RUMStopViewCommand: AnyMockable, RandomMockable { return .mockWith( time: .mockRandomInThePast(), attributes: mockRandomAttributes(), - identity: String.mockRandom() + identity: String.mockRandom().asRUMViewIdentity() ) } static func mockWith( time: Date = Date(), attributes: [AttributeKey: AttributeValue] = [:], - identity: RUMViewIdentifiable = mockView + identity: RUMViewIdentity = mockViewIdentity ) -> RUMStopViewCommand { return RUMStopViewCommand( time: time, attributes: attributes, identity: identity @@ -786,6 +786,7 @@ func createMockView(viewControllerClassName: String) -> UIViewController { ///// Holds the `mockView` object so it can be weakly referenced by `RUMViewScope` mocks. let mockView: UIViewController = createMockViewInWindow() +let mockViewIdentity = mockView.asRUMViewIdentity() extension RUMViewScope { static func mockAny() -> RUMViewScope { @@ -802,7 +803,7 @@ extension RUMViewScope { isInitialView: Bool = false, parent: RUMContextProvider = RUMContextProviderMock(), dependencies: RUMScopeDependencies = .mockAny(), - identity: RUMViewIdentifiable = mockView, + identity: RUMViewIdentity = mockViewIdentity, path: String = .mockAny(), name: String = .mockAny(), attributes: [AttributeKey: AttributeValue] = [:], diff --git a/DatadogCore/Tests/Datadog/RUM/RUMDebuggingTests.swift b/DatadogCore/Tests/Datadog/RUM/RUMDebuggingTests.swift index 555f41b0c6..b3ca57449d 100644 --- a/DatadogCore/Tests/Datadog/RUM/RUMDebuggingTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/RUMDebuggingTests.swift @@ -22,7 +22,7 @@ class RUMDebuggingTests: XCTestCase { dependencies: .mockWith(rumApplicationID: "rum-123") ) _ = applicationScope.process( - command: RUMStartViewCommand.mockWith(identity: mockView, name: "FirstView"), + command: RUMStartViewCommand.mockWith(identity: mockViewIdentity, name: "FirstView"), context: .mockAny(), writer: FileWriterMock() ) @@ -56,7 +56,7 @@ class RUMDebuggingTests: XCTestCase { dependencies: .mockWith(rumApplicationID: "rum-123") ) _ = applicationScope.process( - command: RUMStartViewCommand.mockWith(identity: mockView, name: "FirstView"), + command: RUMStartViewCommand.mockWith(identity: mockViewIdentity, name: "FirstView"), context: context, writer: writer ) @@ -66,7 +66,7 @@ class RUMDebuggingTests: XCTestCase { writer: writer ) _ = applicationScope.process( - command: RUMStartViewCommand.mockWith(identity: mockView, name: "SecondView"), + command: RUMStartViewCommand.mockWith(identity: mockViewIdentity, name: "SecondView"), context: context, writer: writer ) diff --git a/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift b/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift index f8cb24c1c2..ab418b7f74 100644 --- a/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift +++ b/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift @@ -151,14 +151,10 @@ internal final class RUMViewsHandler { return } - guard let identity = view.identity.identifiable else { - return - } - subscriber.process( command: RUMStartViewCommand( time: dateProvider.now, - identity: identity, + identity: view.identity, name: view.name, path: view.path, attributes: view.attributes @@ -167,10 +163,6 @@ internal final class RUMViewsHandler { } private func stop(view: View) { - guard let identity = view.identity.identifiable else { - return - } - guard !view.isUntrackedModal else { return } @@ -179,7 +171,7 @@ internal final class RUMViewsHandler { command: RUMStopViewCommand( time: dateProvider.now, attributes: [:], - identity: identity + identity: view.identity ) ) } @@ -201,14 +193,15 @@ internal final class RUMViewsHandler { extension RUMViewsHandler: UIViewControllerHandler { func notify_viewDidAppear(viewController: UIViewController, animated: Bool) { - if let view = stack.first(where: { $0.identity.equals(viewController) }) { + let identity = viewController.asRUMViewIdentity() + if let view = stack.first(where: { $0.identity.equals(identity) }) { // If the stack already contains the view controller, just restarts the view. // This prevents from calling the predicate when unnecessary. add(view: view) } else if let rumView = predicate?.rumView(for: viewController) { add( view: .init( - identity: viewController.asRUMViewIdentity(), + identity: identity, name: rumView.name, path: rumView.path, isUntrackedModal: rumView.isUntrackedModal, @@ -218,7 +211,7 @@ extension RUMViewsHandler: UIViewControllerHandler { } else if #available(iOS 13, tvOS 13, *), viewController.isModalInPresentation { add( view: .init( - identity: viewController.asRUMViewIdentity(), + identity: identity, name: "RUMUntrackedModal", path: nil, isUntrackedModal: true, diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index dd4aa875b1..07730ee41a 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -234,7 +234,7 @@ extension Monitor: RUMMonitorProtocol { command: RUMStopViewCommand( time: dateProvider.now, attributes: attributes, - identity: viewController + identity: viewController.asRUMViewIdentity() ) ) } @@ -243,7 +243,7 @@ extension Monitor: RUMMonitorProtocol { process( command: RUMStartViewCommand( time: dateProvider.now, - identity: key, + identity: key.asRUMViewIdentity(), name: name ?? key, path: key, attributes: attributes @@ -256,7 +256,7 @@ extension Monitor: RUMMonitorProtocol { command: RUMStopViewCommand( time: dateProvider.now, attributes: attributes, - identity: key + identity: key.asRUMViewIdentity() ) ) } diff --git a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift index 4c9475952f..c0b25c1365 100644 --- a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift +++ b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift @@ -54,7 +54,7 @@ internal struct RUMStartViewCommand: RUMCommand, RUMViewScopePropagatableAttribu let isUserInteraction = true // a new View means there was a navigation, it's considered a User interaction /// The value holding stable identity of the RUM View. - let identity: RUMViewIdentifiable + let identity: RUMViewIdentity /// The name of this View, rendered in RUM Explorer as `VIEW NAME`. let name: String @@ -64,7 +64,7 @@ internal struct RUMStartViewCommand: RUMCommand, RUMViewScopePropagatableAttribu init( time: Date, - identity: RUMViewIdentifiable, + identity: RUMViewIdentity, name: String?, path: String?, attributes: [AttributeKey: AttributeValue] @@ -84,7 +84,7 @@ internal struct RUMStopViewCommand: RUMCommand, RUMViewScopePropagatableAttribut let isUserInteraction = false // a view can be stopped and in most cases should not be considered an interaction (if it's stopped because the user navigate inside the same app, the startView will happen shortly after this) /// The value holding stable identity of the RUM View. - let identity: RUMViewIdentifiable + let identity: RUMViewIdentity } internal struct RUMAddCurrentViewErrorCommand: RUMCommand { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index f6d55697d2..d690c76519 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -83,14 +83,13 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { didStartWithReplay: hasReplay ) - if let viewScope = resumingViewScope, - let viewIdentifiable = viewScope.identity.identifiable { + if let viewScope = resumingViewScope { viewScopes.append( RUMViewScope( isInitialView: false, parent: self, dependencies: dependencies, - identity: viewIdentifiable, + identity: viewScope.identity, path: viewScope.viewPath, name: viewScope.viewName, attributes: viewScope.attributes, @@ -121,14 +120,14 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // Transfer active Views by creating new `RUMViewScopes` for their identity objects: self.viewScopes = expiredSession.viewScopes.compactMap { expiredView in - guard let expiredViewIdentifiable = expiredView.identity.identifiable else { + guard expiredView.identity.isIdentifiable else { return nil // if the underlying identifiable (`UIVIewController`) no longer exists, skip transferring its scope } return RUMViewScope( isInitialView: false, parent: self, dependencies: dependencies, - identity: expiredViewIdentifiable, + identity: expiredView.identity, path: expiredView.viewPath, name: expiredView.viewName, attributes: expiredView.attributes, @@ -233,7 +232,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { isInitialView: true, parent: self, dependencies: dependencies, - identity: RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL, + identity: RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL.asRUMViewIdentity(), path: RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL, name: RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName, attributes: command.attributes, @@ -278,7 +277,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { isInitialView: isStartingInitialView, parent: self, dependencies: dependencies, - identity: RUMOffViewEventsHandlingRule.Constants.backgroundViewURL, + identity: RUMOffViewEventsHandlingRule.Constants.backgroundViewURL.asRUMViewIdentity(), path: RUMOffViewEventsHandlingRule.Constants.backgroundViewURL, name: RUMOffViewEventsHandlingRule.Constants.backgroundViewName, attributes: command.attributes, diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 9cf49bdc7e..09eafc7926 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -96,7 +96,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { isInitialView: Bool, parent: RUMContextProvider, dependencies: RUMScopeDependencies, - identity: RUMViewIdentifiable, + identity: RUMViewIdentity, path: String, name: String, attributes: [AttributeKey: AttributeValue], @@ -107,7 +107,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { self.parent = parent self.dependencies = dependencies self.isInitialView = isInitialView - self.identity = identity.asRUMViewIdentity() + self.identity = identity self.attributes = attributes self.customTimings = customTimings self.viewUUID = dependencies.rumUUIDGenerator.generateUnique() diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift index dc368faa1f..5fb511fe9f 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift @@ -10,7 +10,7 @@ import UIKit /// Based on the `equals(_:)` implementation, it decides if two `RUMViewIdentifiables` identify the same /// RUM View or not. Each implementation of the `RUMViewIdentifiable` decides by its own if it should use /// reference or value semantic for the comparison. -internal protocol RUMViewIdentifiable { +protocol RUMViewIdentifiable { /// Compares the instance of this identifiable with another `RUMViewIdentifiable`. /// It returns `true` if both identify the same RUM View and `false` otherwise. func equals(_ otherIdentifiable: RUMViewIdentifiable?) -> Bool @@ -66,7 +66,7 @@ extension String: RUMViewIdentifiable { // MARK: - `RUMViewIdentity` /// Manages the `RUMViewIdentifiable` by using either reference or value semantic. -internal struct RUMViewIdentity: RUMViewIdentifiable { +internal struct RUMViewIdentity { private weak var object: AnyObject? private let value: Any? @@ -86,13 +86,7 @@ internal struct RUMViewIdentity: RUMViewIdentifiable { /// Returns `true` if a given identifiable indicates the same RUM View as the identifiable managed internally. func equals(_ identifiable: RUMViewIdentifiable?) -> Bool { - if let selfObject = object as? RUMViewIdentifiable { - return selfObject.equals(identifiable) - } else if let selfValue = value as? RUMViewIdentifiable { - return selfValue.equals(identifiable) - } else { - return false - } + return self.identifiable?.equals(identifiable) ?? false } /// Returns `true` if a given identity indicates the same RUM View as the identifiable managed internally. @@ -100,24 +94,18 @@ internal struct RUMViewIdentity: RUMViewIdentifiable { return equals(identity?.identifiable) } - /// Returns the managed identifiable. - var identifiable: RUMViewIdentifiable? { - return (object as? RUMViewIdentifiable) ?? (value as? RUMViewIdentifiable) + /// Return default path name for the managed identifiable. + var defaultViewPath: String { + return identifiable?.defaultViewPath ?? "" } - // MARK: - RUMViewIdentifiable - - func asRUMViewIdentity() -> RUMViewIdentity { - self + /// Returns `true` if the managed identifiable is still available. + var isIdentifiable: Bool { + return identifiable != nil } - var defaultViewPath: String { - if let selfObject = object as? RUMViewIdentifiable { - return selfObject.defaultViewPath - } else if let selfValue = value as? RUMViewIdentifiable { - return selfValue.defaultViewPath - } else { - return "" - } + /// Returns the managed identifiable. + private var identifiable: RUMViewIdentifiable? { + return (object as? RUMViewIdentifiable) ?? (value as? RUMViewIdentifiable) } } diff --git a/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift b/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift index 7548e197b4..5e3fec93ff 100644 --- a/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift +++ b/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift @@ -109,7 +109,9 @@ class RUMViewsHandlerTests: XCTestCase { func testGivenAcceptingPredicate_whenViewDidDisappear_itStartsPreviousRUMView() throws { // Given let view1 = createMockViewInWindow() + let view1Identity = view1.asRUMViewIdentity() let view2 = createMockViewInWindow() + let view2Identity = view2.asRUMViewIdentity() // When predicate.result = .init(name: .mockRandom()) @@ -126,11 +128,11 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[3] as? RUMStopViewCommand) let startCommand3 = try XCTUnwrap(commandSubscriber.receivedCommands[4] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1)) - XCTAssertTrue(stopCommand1.identity.equals(view1)) - XCTAssertTrue(startCommand2.identity.equals(view2)) - XCTAssertTrue(stopCommand2.identity.equals(view2)) - XCTAssertTrue(startCommand3.identity.equals(view1)) + XCTAssertTrue(startCommand1.identity.equals(view1Identity)) + XCTAssertTrue(stopCommand1.identity.equals(view1Identity)) + XCTAssertTrue(startCommand2.identity.equals(view2Identity)) + XCTAssertTrue(stopCommand2.identity.equals(view2Identity)) + XCTAssertTrue(startCommand3.identity.equals(view1Identity)) } func testGivenAcceptingPredicate_whenViewDidDisappearButPreviousView_itDoesNotStartAnyRUMView() { diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 72a8ebacce..2f6887809c 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -165,7 +165,7 @@ extension RUMStartViewCommand: AnyMockable, RandomMockable { return .mockWith( time: .mockRandomInThePast(), attributes: mockRandomAttributes(), - identity: String.mockRandom(), + identity: String.mockRandom().asRUMViewIdentity(), name: .mockRandom(), path: .mockRandom() ) @@ -174,7 +174,7 @@ extension RUMStartViewCommand: AnyMockable, RandomMockable { static func mockWith( time: Date = Date(), attributes: [AttributeKey: AttributeValue] = [:], - identity: RUMViewIdentifiable = mockView, + identity: RUMViewIdentity = mockViewIdentity, name: String = .mockAny(), path: String? = nil ) -> RUMStartViewCommand { @@ -195,14 +195,14 @@ extension RUMStopViewCommand: AnyMockable, RandomMockable { return .mockWith( time: .mockRandomInThePast(), attributes: mockRandomAttributes(), - identity: String.mockRandom() + identity: String.mockRandom().asRUMViewIdentity() ) } static func mockWith( time: Date = Date(), attributes: [AttributeKey: AttributeValue] = [:], - identity: RUMViewIdentifiable = mockView + identity: RUMViewIdentity = mockViewIdentity ) -> RUMStopViewCommand { return RUMStopViewCommand( time: time, attributes: attributes, identity: identity @@ -766,6 +766,7 @@ func createMockView(viewControllerClassName: String) -> UIViewController { ///// Holds the `mockView` object so it can be weakly referenced by `RUMViewScope` mocks. let mockView: UIViewController = createMockViewInWindow() +let mockViewIdentity: RUMViewIdentity = mockView.asRUMViewIdentity() extension RUMViewScope { static func mockAny() -> RUMViewScope { @@ -782,7 +783,7 @@ extension RUMViewScope { isInitialView: Bool = false, parent: RUMContextProvider = RUMContextProviderMock(), dependencies: RUMScopeDependencies = .mockAny(), - identity: RUMViewIdentifiable = mockView, + identity: RUMViewIdentity = mockViewIdentity, path: String = .mockAny(), name: String = .mockAny(), attributes: [AttributeKey: AttributeValue] = [:], diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift index 21fdcc87fb..9d9df1f16a 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift @@ -75,7 +75,7 @@ class RUMApplicationScopeTests: XCTestCase { let view = createMockViewInWindow() _ = scope.process( - command: RUMStartViewCommand.mockWith(time: currentTime, identity: view), + command: RUMStartViewCommand.mockWith(time: currentTime, identity: view.asRUMViewIdentity()), context: context, writer: writer ) @@ -116,12 +116,12 @@ class RUMApplicationScopeTests: XCTestCase { ) _ = scope.process( - command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView), + command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockViewIdentity), context: context, writer: writer ) _ = scope.process( - command: RUMStopViewCommand.mockWith(time: currentTime, identity: mockView), + command: RUMStopViewCommand.mockWith(time: currentTime, identity: mockViewIdentity), context: context, writer: writer ) @@ -139,12 +139,12 @@ class RUMApplicationScopeTests: XCTestCase { ) _ = scope.process( - command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView), + command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockViewIdentity), context: context, writer: writer ) _ = scope.process( - command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView), + command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockViewIdentity), context: context, writer: writer ) @@ -163,12 +163,12 @@ class RUMApplicationScopeTests: XCTestCase { let simulatedSessionsCount = 400 (0.. Date: Tue, 31 Oct 2023 17:48:14 +0000 Subject: [PATCH 3/6] RUM-1978 Refactor to fileprivate --- .../Scopes/Utils/RUMViewIdentity.swift | 8 +- .../Views/RUMViewsHandlerTests.swift | 76 +++++++++---------- .../Scopes/RUMApplicationScopeTests.swift | 2 +- .../Scopes/Utils/RUMViewIdentityTests.swift | 24 +++--- 4 files changed, 54 insertions(+), 56 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift index 5fb511fe9f..72b44b2baf 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift @@ -10,7 +10,7 @@ import UIKit /// Based on the `equals(_:)` implementation, it decides if two `RUMViewIdentifiables` identify the same /// RUM View or not. Each implementation of the `RUMViewIdentifiable` decides by its own if it should use /// reference or value semantic for the comparison. -protocol RUMViewIdentifiable { +fileprivate protocol RUMViewIdentifiable { /// Compares the instance of this identifiable with another `RUMViewIdentifiable`. /// It returns `true` if both identify the same RUM View and `false` otherwise. func equals(_ otherIdentifiable: RUMViewIdentifiable?) -> Bool @@ -27,7 +27,7 @@ protocol RUMViewIdentifiable { /// Extends `UIViewController` with the ability to identify the RUM View. extension UIViewController: RUMViewIdentifiable { - func equals(_ otherIdentifiable: RUMViewIdentifiable?) -> Bool { + fileprivate func equals(_ otherIdentifiable: RUMViewIdentifiable?) -> Bool { if let otherViewController = otherIdentifiable as? UIViewController { // Two `UIViewController` identifiables indicate the same RUM View only if their references are equal. return self === otherViewController @@ -47,7 +47,7 @@ extension UIViewController: RUMViewIdentifiable { /// Extends `String` with the ability to identify the RUM View. extension String: RUMViewIdentifiable { - func equals(_ otherIdentifiable: RUMViewIdentifiable?) -> Bool { + fileprivate func equals(_ otherIdentifiable: RUMViewIdentifiable?) -> Bool { if let otherString = otherIdentifiable as? String { // Two `String` identifiables indicate the same RUM View only if their values are equal. return self == otherString @@ -85,7 +85,7 @@ internal struct RUMViewIdentity { } /// Returns `true` if a given identifiable indicates the same RUM View as the identifiable managed internally. - func equals(_ identifiable: RUMViewIdentifiable?) -> Bool { + fileprivate func equals(_ identifiable: RUMViewIdentifiable?) -> Bool { return self.identifiable?.equals(identifiable) ?? false } diff --git a/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift b/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift index 5e3fec93ff..2c4ae5eca9 100644 --- a/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift +++ b/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift @@ -41,7 +41,7 @@ class RUMViewsHandlerTests: XCTestCase { XCTAssertEqual(commandSubscriber.receivedCommands.count, 1) let command = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) - XCTAssertTrue(command.identity.equals(view)) + XCTAssertTrue(command.identity.equals(view.asRUMViewIdentity())) XCTAssertEqual(command.path, viewControllerClassName) XCTAssertEqual(command.name, viewName) XCTAssertEqual(command.attributes as? [String: String], ["foo": "bar"]) @@ -68,11 +68,11 @@ class RUMViewsHandlerTests: XCTestCase { let startCommand1 = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1)) + XCTAssertTrue(startCommand1.identity.equals(view1.asRUMViewIdentity())) XCTAssertEqual(startCommand1.attributes as? [String: String], ["key1": "val1"]) - XCTAssertTrue(stopCommand.identity.equals(view1)) + XCTAssertTrue(stopCommand.identity.equals(view1.asRUMViewIdentity())) XCTAssertEqual(stopCommand.attributes.count, 0) - XCTAssertTrue(startCommand2.identity.equals(view2)) + XCTAssertTrue(startCommand2.identity.equals(view2.asRUMViewIdentity())) XCTAssertEqual(startCommand2.attributes as? [String: String], ["key2": "val2"]) } @@ -109,9 +109,7 @@ class RUMViewsHandlerTests: XCTestCase { func testGivenAcceptingPredicate_whenViewDidDisappear_itStartsPreviousRUMView() throws { // Given let view1 = createMockViewInWindow() - let view1Identity = view1.asRUMViewIdentity() let view2 = createMockViewInWindow() - let view2Identity = view2.asRUMViewIdentity() // When predicate.result = .init(name: .mockRandom()) @@ -128,11 +126,11 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[3] as? RUMStopViewCommand) let startCommand3 = try XCTUnwrap(commandSubscriber.receivedCommands[4] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1Identity)) - XCTAssertTrue(stopCommand1.identity.equals(view1Identity)) - XCTAssertTrue(startCommand2.identity.equals(view2Identity)) - XCTAssertTrue(stopCommand2.identity.equals(view2Identity)) - XCTAssertTrue(startCommand3.identity.equals(view1Identity)) + XCTAssertTrue(startCommand1.identity.equals(view1.asRUMViewIdentity())) + XCTAssertTrue(stopCommand1.identity.equals(view1.asRUMViewIdentity())) + XCTAssertTrue(startCommand2.identity.equals(view2.asRUMViewIdentity())) + XCTAssertTrue(stopCommand2.identity.equals(view2.asRUMViewIdentity())) + XCTAssertTrue(startCommand3.identity.equals(view1.asRUMViewIdentity())) } func testGivenAcceptingPredicate_whenViewDidDisappearButPreviousView_itDoesNotStartAnyRUMView() { @@ -182,12 +180,12 @@ class RUMViewsHandlerTests: XCTestCase { XCTAssertEqual(commandSubscriber.receivedCommands.count, 3) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) - XCTAssertTrue(stopCommand.identity.equals(view)) + XCTAssertTrue(stopCommand.identity.equals(view.asRUMViewIdentity())) XCTAssertEqual(stopCommand.attributes.count, 0) XCTAssertEqual(stopCommand.time, .mockDecember15th2019At10AMUTC()) let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand.identity.equals(view)) + XCTAssertTrue(startCommand.identity.equals(view.asRUMViewIdentity())) XCTAssertEqual(startCommand.path, viewControllerClassName) XCTAssertEqual(startCommand.name, viewName) XCTAssertEqual(startCommand.attributes as? [String: String], ["foo": "bar"]) @@ -271,8 +269,8 @@ class RUMViewsHandlerTests: XCTestCase { let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) - XCTAssertTrue(startCommand.identity.equals(someView)) - XCTAssertTrue(stopCommand.identity.equals(someView)) + XCTAssertTrue(startCommand.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(someView.asRUMViewIdentity())) } func testGivenUntrackedModal_whenTransitioningToAppearedView_viewDoesStart() throws { @@ -308,9 +306,9 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand.identity.equals(someView)) - XCTAssertTrue(stopCommand.identity.equals(someView)) - XCTAssertTrue(startCommand2.identity.equals(someView)) + XCTAssertTrue(startCommand.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(startCommand2.identity.equals(someView.asRUMViewIdentity())) } func testGiveniOS13AppearedView_whenTransitioningToModal_viewDoesStop() throws { @@ -346,8 +344,8 @@ class RUMViewsHandlerTests: XCTestCase { let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) - XCTAssertTrue(startCommand.identity.equals(someView)) - XCTAssertTrue(stopCommand.identity.equals(someView)) + XCTAssertTrue(startCommand.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(someView.asRUMViewIdentity())) } } @@ -388,9 +386,9 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand.identity.equals(someView)) - XCTAssertTrue(stopCommand.identity.equals(someView)) - XCTAssertTrue(startCommand2.identity.equals(someView)) + XCTAssertTrue(startCommand.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(startCommand2.identity.equals(someView.asRUMViewIdentity())) } } @@ -416,7 +414,7 @@ class RUMViewsHandlerTests: XCTestCase { let command = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) XCTAssertEqual(command.time, .mockDecember15th2019At10AMUTC()) - XCTAssertTrue(command.identity.equals(viewIdentity)) + XCTAssertTrue(command.identity.equals(viewIdentity.asRUMViewIdentity())) XCTAssertEqual(command.name, viewName) XCTAssertEqual(command.path, viewPath) DDAssertDictionariesEqual(command.attributes, viewAttributes) @@ -456,11 +454,11 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1Identity)) + XCTAssertTrue(startCommand1.identity.equals(view1Identity.asRUMViewIdentity())) DDAssertDictionariesEqual(startCommand1.attributes, view1Attributes) - XCTAssertTrue(stopCommand.identity.equals(view1Identity)) + XCTAssertTrue(stopCommand.identity.equals(view1Identity.asRUMViewIdentity())) XCTAssertEqual(stopCommand.attributes.count, 0) - XCTAssertTrue(startCommand2.identity.equals(view2Identity)) + XCTAssertTrue(startCommand2.identity.equals(view2Identity.asRUMViewIdentity())) DDAssertDictionariesEqual(startCommand2.attributes, view2Attributes) } @@ -527,9 +525,9 @@ class RUMViewsHandlerTests: XCTestCase { let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) - XCTAssertTrue(startCommand.identity.equals(viewIdentity)) + XCTAssertTrue(startCommand.identity.equals(viewIdentity.asRUMViewIdentity())) DDAssertDictionariesEqual(startCommand.attributes, viewAttributes) - XCTAssertTrue(stopCommand.identity.equals(viewIdentity)) + XCTAssertTrue(stopCommand.identity.equals(viewIdentity.asRUMViewIdentity())) XCTAssertEqual(stopCommand.attributes.count, 0) } @@ -569,10 +567,10 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand1 = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1Identity)) + XCTAssertTrue(startCommand1.identity.equals(view1Identity.asRUMViewIdentity())) DDAssertDictionariesEqual(startCommand1.attributes, view1Attributes) - XCTAssertTrue(stopCommand1.identity.equals(view1Identity)) - XCTAssertTrue(startCommand2.identity.equals(view2Identity)) + XCTAssertTrue(stopCommand1.identity.equals(view1Identity.asRUMViewIdentity())) + XCTAssertTrue(startCommand2.identity.equals(view2Identity.asRUMViewIdentity())) DDAssertDictionariesEqual(startCommand2.attributes, view2Attributes) } @@ -614,13 +612,13 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[3] as? RUMStopViewCommand) let startCommand3 = try XCTUnwrap(commandSubscriber.receivedCommands[4] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1Identity)) + XCTAssertTrue(startCommand1.identity.equals(view1Identity.asRUMViewIdentity())) DDAssertDictionariesEqual(startCommand1.attributes, view1Attributes) - XCTAssertTrue(stopCommand1.identity.equals(view1Identity)) - XCTAssertTrue(startCommand2.identity.equals(view2Identity)) + XCTAssertTrue(stopCommand1.identity.equals(view1Identity.asRUMViewIdentity())) + XCTAssertTrue(startCommand2.identity.equals(view2Identity.asRUMViewIdentity())) DDAssertDictionariesEqual(startCommand2.attributes, view2Attributes) - XCTAssertTrue(stopCommand2.identity.equals(view2Identity)) - XCTAssertTrue(startCommand3.identity.equals(view1Identity)) + XCTAssertTrue(stopCommand2.identity.equals(view2Identity.asRUMViewIdentity())) + XCTAssertTrue(startCommand3.identity.equals(view1Identity.asRUMViewIdentity())) DDAssertDictionariesEqual(startCommand1.attributes, view1Attributes) } @@ -650,10 +648,10 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(stopCommand.identity.equals(viewIdentity)) + XCTAssertTrue(stopCommand.identity.equals(viewIdentity.asRUMViewIdentity())) XCTAssertEqual(stopCommand.attributes.count, 0) XCTAssertEqual(stopCommand.time, .mockDecember15th2019At10AMUTC()) - XCTAssertTrue(startCommand.identity.equals(viewIdentity)) + XCTAssertTrue(startCommand.identity.equals(viewIdentity.asRUMViewIdentity())) XCTAssertEqual(startCommand.path, viewPath) XCTAssertEqual(startCommand.name, viewName) DDAssertDictionariesEqual(startCommand.attributes, viewAttributes) diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift index 9d9df1f16a..196191db95 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift @@ -101,7 +101,7 @@ class RUMApplicationScopeTests: XCTestCase { let initialViewScope = try XCTUnwrap(initialSession.viewScopes.first) let transferredViewScope = try XCTUnwrap(nextSession.viewScopes.first) XCTAssertNotEqual(initialViewScope.viewUUID, transferredViewScope.viewUUID, "Transferred view scope must have different view id") - XCTAssertTrue(transferredViewScope.identity.equals(view), "Transferred view scope must track the same view") + XCTAssertTrue(transferredViewScope.identity.equals(view.asRUMViewIdentity()), "Transferred view scope must track the same view") XCTAssertFalse(nextSession.isInitialSession, "Any next session in the application must be marked as 'not initial'") } diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift index 8bb8806028..2a51eb92e2 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift @@ -22,13 +22,13 @@ class RUMViewIdentityTests: XCTestCase { let identity2 = vc2.asRUMViewIdentity() // Then - XCTAssertTrue(identity1.equals(vc1)) - XCTAssertTrue(identity2.equals(vc2)) + XCTAssertTrue(identity1.equals(vc1.asRUMViewIdentity())) + XCTAssertTrue(identity2.equals(vc2.asRUMViewIdentity())) XCTAssertTrue(identity1.equals(identity1)) - XCTAssertFalse(identity1.equals(vc2)) - XCTAssertFalse(identity2.equals(vc1)) + XCTAssertFalse(identity1.equals(vc2.asRUMViewIdentity())) + XCTAssertFalse(identity2.equals(vc1.asRUMViewIdentity())) XCTAssertFalse(identity1.equals(identity2)) - XCTAssertFalse(identity1.equals(vc3)) + XCTAssertFalse(identity1.equals(vc3?.asRUMViewIdentity())) } func testGivenTwoStringKeys_whenComparingTheirRUMViewIdentity_itEqualsOnlyForTheSameInstance() { @@ -42,13 +42,13 @@ class RUMViewIdentityTests: XCTestCase { let identity2 = key2.asRUMViewIdentity() // Then - XCTAssertTrue(identity1.equals(key1)) - XCTAssertTrue(identity2.equals(key2)) + XCTAssertTrue(identity1.equals(key1.asRUMViewIdentity())) + XCTAssertTrue(identity2.equals(key2.asRUMViewIdentity())) XCTAssertTrue(identity1.equals(identity1)) - XCTAssertFalse(identity1.equals(key2)) - XCTAssertFalse(identity2.equals(key1)) + XCTAssertFalse(identity1.equals(key2.asRUMViewIdentity())) + XCTAssertFalse(identity2.equals(key1.asRUMViewIdentity())) XCTAssertFalse(identity1.equals(identity2)) - XCTAssertFalse(identity1.equals(key3)) + XCTAssertFalse(identity1.equals(key3?.asRUMViewIdentity())) } func testGivenTwoRUMViewIdentitiesOfDifferentKind_whenComparing_theyDoNotEqual() { @@ -61,8 +61,8 @@ class RUMViewIdentityTests: XCTestCase { let identity2 = key.asRUMViewIdentity() // Then - XCTAssertFalse(identity1.equals(key)) - XCTAssertFalse(identity2.equals(vc)) + XCTAssertFalse(identity1.equals(key.asRUMViewIdentity())) + XCTAssertFalse(identity2.equals(vc.asRUMViewIdentity())) } // MARK: - Retrieving properties From 751c8a2afecde0d8fc978bd7310a258582dad7f1 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Tue, 31 Oct 2023 17:59:24 +0000 Subject: [PATCH 4/6] RUM-1978 Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 412dc628f7..4b5c7931b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - [BUGFIX] Optimize Session Replay diffing algorithm. See [#1524][] - [FEATURE] Add network instrumentation for async/await URLSession APIs. See [#1394][] - [FEATURE] Change default tracing headers for first party hosts to use both Datadog headers and W3C `tracecontext` headers. See [#1529][] +- [BUGFIX] Fix RUM ViewController leaks. See [#1533][] # 2.4.0 / 18-10-2023 @@ -550,6 +551,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1394]: https://github.com/DataDog/dd-sdk-ios/pull/1394 [#1524]: https://github.com/DataDog/dd-sdk-ios/pull/1524 [#1529]: https://github.com/DataDog/dd-sdk-ios/pull/1529 +[#1533]: https://github.com/DataDog/dd-sdk-ios/pull/1533 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu From 8c7029795474f4b69d7446ffc2c5c39d17bd2c77 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Tue, 31 Oct 2023 18:04:31 +0000 Subject: [PATCH 5/6] RUM-1978 PR fixes --- Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift | 3 +-- .../Sources/Instrumentation/Views/RUMViewsHandler.swift | 8 ++++++++ .../Sources/RUMMonitor/Scopes/RUMSessionScope.swift | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift b/Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift index 7048182a20..b34897b0db 100644 --- a/Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift +++ b/Datadog/E2ETests/RUM/RUMMonitorE2ETests.swift @@ -14,8 +14,7 @@ class RUMMonitorE2ETests: E2ETests { let actionTypePool = [RUMActionType.swipe, .scroll, .tap, .custom] let nonCustomActionTypePool = [RUMActionType.swipe, .scroll, .tap] - /// - api-surface: RUMMonitorProtocol.startView(key: String, -ame: String? = nil,attributes: [AttributeKey: AttributeValue] = [:]) + /// - api-surface: RUMMonitorProtocol.startView(key: String,name: String? = nil,attributes: [AttributeKey: AttributeValue] = [:]) /// /// - data monitor: /// ```rum diff --git a/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift b/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift index ab418b7f74..33049d6b63 100644 --- a/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift +++ b/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift @@ -151,6 +151,10 @@ internal final class RUMViewsHandler { return } + guard view.identity.isIdentifiable else { + return + } + subscriber.process( command: RUMStartViewCommand( time: dateProvider.now, @@ -163,6 +167,10 @@ internal final class RUMViewsHandler { } private func stop(view: View) { + guard view.identity.isIdentifiable else { + return + } + guard !view.isUntrackedModal else { return } diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index d690c76519..a4e52cc5cc 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -83,7 +83,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { didStartWithReplay: hasReplay ) - if let viewScope = resumingViewScope { + if let viewScope = resumingViewScope, viewScope.identity.isIdentifiable { viewScopes.append( RUMViewScope( isInitialView: false, From 519bc67a88730ad1980472e569abfe36c06bcbf0 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Fri, 3 Nov 2023 16:24:44 +0000 Subject: [PATCH 6/6] RUM-1978 PR Fixes --- .../Views/RUMViewsHandler.swift | 4 +- .../RUMMonitor/Scopes/RUMSessionScope.swift | 4 +- .../Scopes/Utils/RUMViewIdentity.swift | 4 +- .../Views/RUMViewsHandlerTests.swift | 74 +++++++++---------- .../Scopes/RUMApplicationScopeTests.swift | 2 +- .../Scopes/Utils/RUMViewIdentityTests.swift | 42 +++++++---- 6 files changed, 71 insertions(+), 59 deletions(-) diff --git a/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift b/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift index 33049d6b63..9dc5b80807 100644 --- a/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift +++ b/DatadogRUM/Sources/Instrumentation/Views/RUMViewsHandler.swift @@ -151,7 +151,7 @@ internal final class RUMViewsHandler { return } - guard view.identity.isIdentifiable else { + guard view.identity.exists else { return } @@ -167,7 +167,7 @@ internal final class RUMViewsHandler { } private func stop(view: View) { - guard view.identity.isIdentifiable else { + guard view.identity.exists else { return } diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index a4e52cc5cc..40c3567342 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -83,7 +83,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { didStartWithReplay: hasReplay ) - if let viewScope = resumingViewScope, viewScope.identity.isIdentifiable { + if let viewScope = resumingViewScope, viewScope.identity.exists { viewScopes.append( RUMViewScope( isInitialView: false, @@ -120,7 +120,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // Transfer active Views by creating new `RUMViewScopes` for their identity objects: self.viewScopes = expiredSession.viewScopes.compactMap { expiredView in - guard expiredView.identity.isIdentifiable else { + guard expiredView.identity.exists else { return nil // if the underlying identifiable (`UIVIewController`) no longer exists, skip transferring its scope } return RUMViewScope( diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift index 72b44b2baf..6a169030fc 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift @@ -100,7 +100,9 @@ internal struct RUMViewIdentity { } /// Returns `true` if the managed identifiable is still available. - var isIdentifiable: Bool { + /// Underlying `identfiable` is stored as a weak reference, so it may become `nil` at any time. + /// For example when the `UIViewController` is deallocated. + var exists: Bool { return identifiable != nil } diff --git a/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift b/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift index 2c4ae5eca9..7548e197b4 100644 --- a/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift +++ b/DatadogRUM/Tests/Instrumentation/Views/RUMViewsHandlerTests.swift @@ -41,7 +41,7 @@ class RUMViewsHandlerTests: XCTestCase { XCTAssertEqual(commandSubscriber.receivedCommands.count, 1) let command = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) - XCTAssertTrue(command.identity.equals(view.asRUMViewIdentity())) + XCTAssertTrue(command.identity.equals(view)) XCTAssertEqual(command.path, viewControllerClassName) XCTAssertEqual(command.name, viewName) XCTAssertEqual(command.attributes as? [String: String], ["foo": "bar"]) @@ -68,11 +68,11 @@ class RUMViewsHandlerTests: XCTestCase { let startCommand1 = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1.asRUMViewIdentity())) + XCTAssertTrue(startCommand1.identity.equals(view1)) XCTAssertEqual(startCommand1.attributes as? [String: String], ["key1": "val1"]) - XCTAssertTrue(stopCommand.identity.equals(view1.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(view1)) XCTAssertEqual(stopCommand.attributes.count, 0) - XCTAssertTrue(startCommand2.identity.equals(view2.asRUMViewIdentity())) + XCTAssertTrue(startCommand2.identity.equals(view2)) XCTAssertEqual(startCommand2.attributes as? [String: String], ["key2": "val2"]) } @@ -126,11 +126,11 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[3] as? RUMStopViewCommand) let startCommand3 = try XCTUnwrap(commandSubscriber.receivedCommands[4] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1.asRUMViewIdentity())) - XCTAssertTrue(stopCommand1.identity.equals(view1.asRUMViewIdentity())) - XCTAssertTrue(startCommand2.identity.equals(view2.asRUMViewIdentity())) - XCTAssertTrue(stopCommand2.identity.equals(view2.asRUMViewIdentity())) - XCTAssertTrue(startCommand3.identity.equals(view1.asRUMViewIdentity())) + XCTAssertTrue(startCommand1.identity.equals(view1)) + XCTAssertTrue(stopCommand1.identity.equals(view1)) + XCTAssertTrue(startCommand2.identity.equals(view2)) + XCTAssertTrue(stopCommand2.identity.equals(view2)) + XCTAssertTrue(startCommand3.identity.equals(view1)) } func testGivenAcceptingPredicate_whenViewDidDisappearButPreviousView_itDoesNotStartAnyRUMView() { @@ -180,12 +180,12 @@ class RUMViewsHandlerTests: XCTestCase { XCTAssertEqual(commandSubscriber.receivedCommands.count, 3) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) - XCTAssertTrue(stopCommand.identity.equals(view.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(view)) XCTAssertEqual(stopCommand.attributes.count, 0) XCTAssertEqual(stopCommand.time, .mockDecember15th2019At10AMUTC()) let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand.identity.equals(view.asRUMViewIdentity())) + XCTAssertTrue(startCommand.identity.equals(view)) XCTAssertEqual(startCommand.path, viewControllerClassName) XCTAssertEqual(startCommand.name, viewName) XCTAssertEqual(startCommand.attributes as? [String: String], ["foo": "bar"]) @@ -269,8 +269,8 @@ class RUMViewsHandlerTests: XCTestCase { let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) - XCTAssertTrue(startCommand.identity.equals(someView.asRUMViewIdentity())) - XCTAssertTrue(stopCommand.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(startCommand.identity.equals(someView)) + XCTAssertTrue(stopCommand.identity.equals(someView)) } func testGivenUntrackedModal_whenTransitioningToAppearedView_viewDoesStart() throws { @@ -306,9 +306,9 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand.identity.equals(someView.asRUMViewIdentity())) - XCTAssertTrue(stopCommand.identity.equals(someView.asRUMViewIdentity())) - XCTAssertTrue(startCommand2.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(startCommand.identity.equals(someView)) + XCTAssertTrue(stopCommand.identity.equals(someView)) + XCTAssertTrue(startCommand2.identity.equals(someView)) } func testGiveniOS13AppearedView_whenTransitioningToModal_viewDoesStop() throws { @@ -344,8 +344,8 @@ class RUMViewsHandlerTests: XCTestCase { let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) - XCTAssertTrue(startCommand.identity.equals(someView.asRUMViewIdentity())) - XCTAssertTrue(stopCommand.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(startCommand.identity.equals(someView)) + XCTAssertTrue(stopCommand.identity.equals(someView)) } } @@ -386,9 +386,9 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand.identity.equals(someView.asRUMViewIdentity())) - XCTAssertTrue(stopCommand.identity.equals(someView.asRUMViewIdentity())) - XCTAssertTrue(startCommand2.identity.equals(someView.asRUMViewIdentity())) + XCTAssertTrue(startCommand.identity.equals(someView)) + XCTAssertTrue(stopCommand.identity.equals(someView)) + XCTAssertTrue(startCommand2.identity.equals(someView)) } } @@ -414,7 +414,7 @@ class RUMViewsHandlerTests: XCTestCase { let command = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) XCTAssertEqual(command.time, .mockDecember15th2019At10AMUTC()) - XCTAssertTrue(command.identity.equals(viewIdentity.asRUMViewIdentity())) + XCTAssertTrue(command.identity.equals(viewIdentity)) XCTAssertEqual(command.name, viewName) XCTAssertEqual(command.path, viewPath) DDAssertDictionariesEqual(command.attributes, viewAttributes) @@ -454,11 +454,11 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1Identity.asRUMViewIdentity())) + XCTAssertTrue(startCommand1.identity.equals(view1Identity)) DDAssertDictionariesEqual(startCommand1.attributes, view1Attributes) - XCTAssertTrue(stopCommand.identity.equals(view1Identity.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(view1Identity)) XCTAssertEqual(stopCommand.attributes.count, 0) - XCTAssertTrue(startCommand2.identity.equals(view2Identity.asRUMViewIdentity())) + XCTAssertTrue(startCommand2.identity.equals(view2Identity)) DDAssertDictionariesEqual(startCommand2.attributes, view2Attributes) } @@ -525,9 +525,9 @@ class RUMViewsHandlerTests: XCTestCase { let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[0] as? RUMStartViewCommand) let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) - XCTAssertTrue(startCommand.identity.equals(viewIdentity.asRUMViewIdentity())) + XCTAssertTrue(startCommand.identity.equals(viewIdentity)) DDAssertDictionariesEqual(startCommand.attributes, viewAttributes) - XCTAssertTrue(stopCommand.identity.equals(viewIdentity.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(viewIdentity)) XCTAssertEqual(stopCommand.attributes.count, 0) } @@ -567,10 +567,10 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand1 = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1Identity.asRUMViewIdentity())) + XCTAssertTrue(startCommand1.identity.equals(view1Identity)) DDAssertDictionariesEqual(startCommand1.attributes, view1Attributes) - XCTAssertTrue(stopCommand1.identity.equals(view1Identity.asRUMViewIdentity())) - XCTAssertTrue(startCommand2.identity.equals(view2Identity.asRUMViewIdentity())) + XCTAssertTrue(stopCommand1.identity.equals(view1Identity)) + XCTAssertTrue(startCommand2.identity.equals(view2Identity)) DDAssertDictionariesEqual(startCommand2.attributes, view2Attributes) } @@ -612,13 +612,13 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand2 = try XCTUnwrap(commandSubscriber.receivedCommands[3] as? RUMStopViewCommand) let startCommand3 = try XCTUnwrap(commandSubscriber.receivedCommands[4] as? RUMStartViewCommand) - XCTAssertTrue(startCommand1.identity.equals(view1Identity.asRUMViewIdentity())) + XCTAssertTrue(startCommand1.identity.equals(view1Identity)) DDAssertDictionariesEqual(startCommand1.attributes, view1Attributes) - XCTAssertTrue(stopCommand1.identity.equals(view1Identity.asRUMViewIdentity())) - XCTAssertTrue(startCommand2.identity.equals(view2Identity.asRUMViewIdentity())) + XCTAssertTrue(stopCommand1.identity.equals(view1Identity)) + XCTAssertTrue(startCommand2.identity.equals(view2Identity)) DDAssertDictionariesEqual(startCommand2.attributes, view2Attributes) - XCTAssertTrue(stopCommand2.identity.equals(view2Identity.asRUMViewIdentity())) - XCTAssertTrue(startCommand3.identity.equals(view1Identity.asRUMViewIdentity())) + XCTAssertTrue(stopCommand2.identity.equals(view2Identity)) + XCTAssertTrue(startCommand3.identity.equals(view1Identity)) DDAssertDictionariesEqual(startCommand1.attributes, view1Attributes) } @@ -648,10 +648,10 @@ class RUMViewsHandlerTests: XCTestCase { let stopCommand = try XCTUnwrap(commandSubscriber.receivedCommands[1] as? RUMStopViewCommand) let startCommand = try XCTUnwrap(commandSubscriber.receivedCommands[2] as? RUMStartViewCommand) - XCTAssertTrue(stopCommand.identity.equals(viewIdentity.asRUMViewIdentity())) + XCTAssertTrue(stopCommand.identity.equals(viewIdentity)) XCTAssertEqual(stopCommand.attributes.count, 0) XCTAssertEqual(stopCommand.time, .mockDecember15th2019At10AMUTC()) - XCTAssertTrue(startCommand.identity.equals(viewIdentity.asRUMViewIdentity())) + XCTAssertTrue(startCommand.identity.equals(viewIdentity)) XCTAssertEqual(startCommand.path, viewPath) XCTAssertEqual(startCommand.name, viewName) DDAssertDictionariesEqual(startCommand.attributes, viewAttributes) diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift index 196191db95..9d9df1f16a 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift @@ -101,7 +101,7 @@ class RUMApplicationScopeTests: XCTestCase { let initialViewScope = try XCTUnwrap(initialSession.viewScopes.first) let transferredViewScope = try XCTUnwrap(nextSession.viewScopes.first) XCTAssertNotEqual(initialViewScope.viewUUID, transferredViewScope.viewUUID, "Transferred view scope must have different view id") - XCTAssertTrue(transferredViewScope.identity.equals(view.asRUMViewIdentity()), "Transferred view scope must track the same view") + XCTAssertTrue(transferredViewScope.identity.equals(view), "Transferred view scope must track the same view") XCTAssertFalse(nextSession.isInitialSession, "Any next session in the application must be marked as 'not initial'") } diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift index 2a51eb92e2..afd7865327 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift @@ -22,13 +22,13 @@ class RUMViewIdentityTests: XCTestCase { let identity2 = vc2.asRUMViewIdentity() // Then - XCTAssertTrue(identity1.equals(vc1.asRUMViewIdentity())) - XCTAssertTrue(identity2.equals(vc2.asRUMViewIdentity())) + XCTAssertTrue(identity1.equals(vc1)) + XCTAssertTrue(identity2.equals(vc2)) XCTAssertTrue(identity1.equals(identity1)) - XCTAssertFalse(identity1.equals(vc2.asRUMViewIdentity())) - XCTAssertFalse(identity2.equals(vc1.asRUMViewIdentity())) + XCTAssertFalse(identity1.equals(vc2)) + XCTAssertFalse(identity2.equals(vc1)) XCTAssertFalse(identity1.equals(identity2)) - XCTAssertFalse(identity1.equals(vc3?.asRUMViewIdentity())) + XCTAssertFalse(identity1.equals(vc3)) } func testGivenTwoStringKeys_whenComparingTheirRUMViewIdentity_itEqualsOnlyForTheSameInstance() { @@ -42,13 +42,13 @@ class RUMViewIdentityTests: XCTestCase { let identity2 = key2.asRUMViewIdentity() // Then - XCTAssertTrue(identity1.equals(key1.asRUMViewIdentity())) - XCTAssertTrue(identity2.equals(key2.asRUMViewIdentity())) + XCTAssertTrue(identity1.equals(key1)) + XCTAssertTrue(identity2.equals(key2)) XCTAssertTrue(identity1.equals(identity1)) - XCTAssertFalse(identity1.equals(key2.asRUMViewIdentity())) - XCTAssertFalse(identity2.equals(key1.asRUMViewIdentity())) + XCTAssertFalse(identity1.equals(key2)) + XCTAssertFalse(identity2.equals(key1)) XCTAssertFalse(identity1.equals(identity2)) - XCTAssertFalse(identity1.equals(key3?.asRUMViewIdentity())) + XCTAssertFalse(identity1.equals(key3)) } func testGivenTwoRUMViewIdentitiesOfDifferentKind_whenComparing_theyDoNotEqual() { @@ -61,8 +61,8 @@ class RUMViewIdentityTests: XCTestCase { let identity2 = key.asRUMViewIdentity() // Then - XCTAssertFalse(identity1.equals(key.asRUMViewIdentity())) - XCTAssertFalse(identity2.equals(vc.asRUMViewIdentity())) + XCTAssertFalse(identity1.equals(key)) + XCTAssertFalse(identity2.equals(vc)) } // MARK: - Retrieving properties @@ -82,8 +82,8 @@ class RUMViewIdentityTests: XCTestCase { let identity1 = vc.asRUMViewIdentity() let identity2 = key.asRUMViewIdentity() - XCTAssertTrue(identity1.isIdentifiable) - XCTAssertTrue(identity2.isIdentifiable) + XCTAssertTrue(identity1.exists) + XCTAssertTrue(identity2.exists) } // MARK: - Memory management @@ -94,10 +94,20 @@ class RUMViewIdentityTests: XCTestCase { try autoreleasepool { var vc: UIViewController? = UIViewController() identity = try XCTUnwrap(vc?.asRUMViewIdentity()) - XCTAssertTrue(identity.isIdentifiable, "Reference should be available while `vc` is alive.") + XCTAssertTrue(identity.exists, "Reference should be available while `vc` is alive.") vc = nil } - XCTAssertFalse(identity.isIdentifiable, "Reference should not be available after `vc` was deallocated.") + XCTAssertFalse(identity.exists, "Reference should not be available after `vc` was deallocated.") + } +} + +extension RUMViewIdentity { + func equals(_ vc: UIViewController?) -> Bool { + return equals(vc?.asRUMViewIdentity()) + } + + func equals(_ string: String?) -> Bool { + return equals(string?.asRUMViewIdentity()) } }