Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUMM-1097 Fix DefaultUIKitRUMViewsPredicate for Objc view controllers #419

Merged

Conversation

ncreated
Copy link
Member

@ncreated ncreated commented Feb 18, 2021

What and why?

🐛 When RUM Views auto instrumentation was enabled, no UIViewController defined in Objc was tracked. No matter if the SDK was configured in Objc:

[configuration trackUIKitRUMViews];

or swift:

.trackUIKitRUMViews()

How?

The problem was caused by the filtering logic done in DefaultUIKitRUMViewsPredicate:

public func rumView(for viewController: UIViewController) -> RUMView? {
   let canonicalClassName = NSStringFromClass(type(of: viewController))
   let isCustomClass = canonicalClassName.contains(".") // custom class contains module prefix

   if isCustomClass {
      // ... capture the view
   } else {
      return nil // ignore the view
   }
}

isCustomClass was meant to indicate if the viewController is a custom VC provided by the user app (true) or is a standard VC coming from UIKit (false). This was to ignore all the "system" view controllers like UINavigationViewController, UITabBarController etc.

🐛 The implementation was based on the observation that custom Swift VCs are prefixed with module name when inspecting their name through NSStringFromClass() (e.g. MyApp.LoginViewController). This turned out to be not true for custom Objc VCs, which have no module prefix.

🌟 The fix is to change the way we recognise custom VC. Now we lookup the Bundle that the class is loaded from and we simply filter out all VCs coming from UIKitCore.framework bundle.

Why it was not catched before?

This bug was not visible, because we didn't have any tests covering Obj-C view controllers and RUM Views predicate.

How to prevent it in the future?

I added one unit test for covering this exact scenario and configured one of the integration tests to also work with Objective-C view controllers (to cover other potential issues of mixed Swift <> Objc codebase instrumentation).

The integration test was done by splitting the existing RUMResourcesScenario into two separate scenarios: RUMURLSessionResourcesScenario and RUMNSURLSessionResourcesScenario. They both have identical setup, storyboards and view controller implementations. The prior defines its VCs in Swift, the latter in Objective-C. It didn't require adding anything new in the UI, as those tests are based on the existing URLSession + NSURLSession scenarios.

Screenshot 2021-02-18 at 18 41 48

As a bonus 🎁 we gain integration coverage for RUM Resources tracked from Obj-c code.

Review checklist

  • Feature or bugfix MUST have appropriate tests (unit, integration)
  • Make sure each commit and the PR mention the Issue number or JIRA reference

@ncreated ncreated marked this pull request as ready for review February 18, 2021 18:00
@ncreated ncreated requested a review from a team as a code owner February 18, 2021 18:00
@ncreated ncreated self-assigned this Feb 19, 2021
Copy link
Contributor

@acgh acgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great 💪

@@ -8,16 +8,31 @@ import XCTest
import Datadog

class UIKitRUMViewsPredicateTests: XCTestCase {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also add test cases to check that some known UIKit classes are filtered out, to catch any regressions of the internal isUIKit with future releases or iOS.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to @acgh , we rely on private information (UIKitCore is private) and that may break at any time

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already there - just not visible in this diff. We had this test before:

    func testGivenDefaultPredicate_whenAskingUIKitViewController_itReturnsNoView() {
        // Given
        let predicate = DefaultUIKitRUMViewsPredicate()
        // When
        let uiKitViewController = UIViewController()
        let rumView = predicate.rumView(for: uiKitViewController)
        // Then
        XCTAssertNil(rumView)
    }

}

/// If given `class` comes from UIKit framework.
private func isUIKit(`class`: AnyClass) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Put this as an internal extension on Bundle in the UIKitExtensions.swift file

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean sth like Bundle.isUIKit(class:) -> Bool? This would read weird, no? This seems to be rather a characteristic of the AnyClass than a Bundle - but as AnyClass is a metatype it cannot be extended.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not important for me but i was thinking of smth like this:

// extension
extension AnyObject {
  var isUIKit: Bool { ... }
}
// calling site
if viewController.isUIKit { return }
else { ... }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👌 , I added:

internal extension Bundle {
    var isUIKit: Bool
}

extension and bunch of fixtures in tests.

Copy link
Contributor

@buranmert buranmert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we try it in a real device? IMO it's unlikely but bundleURL may be different there

Comment on lines 61 to 62
guard !isUIKit(class: type(of: viewController)) else {
// do not track bare UIKit's view controllers (UINavigationController, UITabBarController, ...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the real goal is excluding container view controllers, exclusion of UIKit types is just the workaround that we could come up with.
this comment here may be misleading if we come back to this issue later on, can we correct it?

here is the same issue in PR's description as well:

...This was to ignore all the "system" view controllers...

Copy link
Member Author

@ncreated ncreated Feb 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's about plain (not subclassed / customized) VCs from UIKit.

Before this PR, this implementation was considering MyCustomNavigationViewController as the RUM View. I think we shouldn't change that default if no one has reported it being wrong. We're doing a bug fix here, I wouldn't change the previous behaviour.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no i'm not talking about the behavior, my concern is that the comments don't tell the whole story.
we first wanted to exclude containers and then noticed invisible view controllers in the hiearchy. if one asks "why do we exclude UIKit types?", the answer is not obvious without this story.

IMO this comment do not track UIKit controllers brings questions (why?) rather than answering

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see 👍. Improved it with discussing the reasoning and optimisation.

@@ -8,16 +8,31 @@ import XCTest
import Datadog

class UIKitRUMViewsPredicateTests: XCTestCase {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to @acgh , we rely on private information (UIKitCore is private) and that may break at any time

@ncreated ncreated requested a review from buranmert February 22, 2021 10:51
Base automatically changed from ncreated/RUMM-1030-use-name-property-to-customize-RUM-View-names to master February 22, 2021 12:48
@ncreated ncreated merged commit c2b4e64 into master Feb 22, 2021
@ncreated ncreated deleted the ncreated/RUMM-1097-fix-default-rum-predicate-for-objc branch February 22, 2021 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants