Skip to content

Commit

Permalink
RUMM-1745 Use single handler for UIKit and SwiftUI views instrumentation
Browse files Browse the repository at this point in the history
  • Loading branch information
maxep committed Nov 5, 2021
1 parent db8df52 commit fa81f82
Show file tree
Hide file tree
Showing 20 changed files with 488 additions and 690 deletions.
48 changes: 16 additions & 32 deletions Datadog/Datadog.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Datadog/Example/Scenarios/RUM/RUMScenarios.swift
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ final class RUMSwiftUIInstrumentationScenario: TestScenario {
return nil
}

if let viewController = viewController as? UIScreenViewController {
return .init(name: "UIKit View \(viewController.index)")
}

return `default`.rumView(for: viewController)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,51 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="139" y="125"/>
<point key="canvasLocation" x="-116" y="104"/>
</scene>
<!--Screen View Controller-->
<scene sceneID="L6z-DM-TUo">
<objects>
<viewController storyboardIdentifier="UIScreenViewController" id="sHo-CT-xwJ" customClass="UIScreenViewController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EAT-Wj-KAZ">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="32" translatesAutoresizingMaskIntoConstraints="NO" id="M5C-En-s9f">
<rect key="frame" x="139" y="402" width="136" height="92"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eHI-Ld-0x4">
<rect key="frame" x="0.0" y="0.0" width="136" height="30"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Push to Next View"/>
<connections>
<action selector="pushToSwiftUIView:" destination="sHo-CT-xwJ" eventType="touchUpInside" id="INd-ah-hu1"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="I9l-zs-IwU">
<rect key="frame" x="0.0" y="62" width="136" height="30"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Present Modal View"/>
<connections>
<action selector="presentSheet:" destination="sHo-CT-xwJ" eventType="touchUpInside" id="hzL-jT-z9i"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="1wT-3f-fiV"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="M5C-En-s9f" firstAttribute="centerX" secondItem="EAT-Wj-KAZ" secondAttribute="centerX" id="2Yn-5f-1re"/>
<constraint firstItem="M5C-En-s9f" firstAttribute="centerY" secondItem="EAT-Wj-KAZ" secondAttribute="centerY" id="IAL-J6-XlF"/>
<constraint firstItem="M5C-En-s9f" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="1wT-3f-fiV" secondAttribute="leading" id="Nxo-ex-v1a"/>
<constraint firstItem="M5C-En-s9f" firstAttribute="top" relation="greaterThanOrEqual" secondItem="1wT-3f-fiV" secondAttribute="top" id="eHL-xe-TPx"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bi0-Ck-Xb2" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="557" y="104"/>
</scene>
</scenes>
<resources>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,7 @@ struct ScreenView: View {

var body: some View {
VStack(spacing: 32) {
NavigationLink("Push to Next View", destination:
ScreenView(index: index + 1)
)
NavigationLink("Push to Next View", destination: destination)
Button("Present Modal View") {
presentSheet.toggle()
}
Expand All @@ -112,6 +110,78 @@ struct ScreenView: View {

@ViewBuilder
var destination: some View {
ScreenView(index: index + 1)
if index % 3 == 0 {
UIScreenView(index: index + 1)
.navigationBarTitle("Screen \(index)")
} else {
ScreenView(index: index + 1)
}
}
}

/// The `UIScreenView` is a `UIScreenViewController` respresentable
/// for SwiftUI.
@available(iOS 13, *)
struct UIScreenView: UIViewControllerRepresentable {

/// The screen index in the stack
let index: Int

func makeUIViewController(context: Context) -> UIScreenViewController {
UIScreenViewController.create(at: index)
}

func updateUIViewController(_ uiViewController: UIScreenViewController, context: Context) { }
}

/// A basic Screen View Controller at a given index in the stack.
///
/// This view controller present a single button to push a
/// `ScreenView` onto the stack.
@available(iOS 13, *)
class UIScreenViewController: UIViewController {

var index: Int = 0

/// Creates a `UIScreenViewController` instance from `RUMSwiftUIInstrumentationScenario` storyboard.
///
/// - Parameter index: The Screen index in the stack.
/// - Returns: An instance of `UIScreenViewController`.
static func create(at index: Int) -> UIScreenViewController {
let storyboard = UIStoryboard(name: "RUMSwiftUIInstrumentationScenario", bundle: .main)

if let vc = storyboard.instantiateViewController(withIdentifier: "UIScreenViewController") as? UIScreenViewController {
vc.index = index
return vc
}

fatalError("Unable to instantiate `UIScreenViewController` from stroyboard `RUMSwiftUIInstrumentationScenario`")
}

/// Pushes a `ScreenView` onto the stack.
@IBAction func pushToSwiftUIView(_ sender: Any?) {
let view = ScreenView(index: index + 1)
let host = UIHostingController(rootView: view)
navigationController?.show(host, sender: sender)
}

/// Pushes a `ScreenView` onto the stack.
@IBAction func presentSheet(_ sender: Any?) {
let host = UIHostingController(rootView: sheetView)
host.modalPresentationStyle = .pageSheet
present(host, animated: true)
}

@ViewBuilder
var sheetView: some View {
NavigationView {
if index % 3 == 0 {
UIScreenView(index: index + 1)
.navigationBarTitle("Screen \(index)")
} else {
ScreenView(index: index + 1)
}
}
}
}

40 changes: 9 additions & 31 deletions Sources/Datadog/RUM/Instrumentation/RUMInstrumentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,6 @@ import Foundation
internal final class RUMInstrumentation: RUMCommandPublisher {
static var instance: RUMInstrumentation?

/// RUM Views auto instrumentation.
class ViewsAutoInstrumentation {
let swizzler: UIViewControllerSwizzler
let handler: UIViewControllerHandler

init(
predicate: UIKitRUMViewsPredicate,
dateProvider: DateProvider
) throws {
handler = UIKitRUMViewsHandler(
predicate: predicate,
dateProvider: dateProvider
)
swizzler = try UIViewControllerSwizzler(handler: handler)
}

func enable() {
swizzler.swizzle()
}
}

/// RUM User Actions auto instrumentation.
class UserActionsAutoInstrumentation {
let swizzler: UIApplicationSwizzler
Expand All @@ -46,10 +25,9 @@ internal final class RUMInstrumentation: RUMCommandPublisher {
}
}

let viewsInstrumentation: RUMViewsHandler
/// RUM Views auto instrumentation, `nil` if not enabled.
let viewsAutoInstrumentation: ViewsAutoInstrumentation?
/// `SwiftUI.View` RUM instrumentation
let swiftUIViewInstrumentation: SwiftUIViewHandler
let viewsAutoInstrumentation: UIViewControllerSwizzler?
/// RUM User Actions auto instrumentation, `nil` if not enabled.
let userActionsAutoInstrumentation: UserActionsAutoInstrumentation?
/// RUM Long Tasks auto instrumentation, `nil` if not enabled.
Expand All @@ -61,13 +39,15 @@ internal final class RUMInstrumentation: RUMCommandPublisher {
configuration: FeaturesConfiguration.RUM.Instrumentation,
dateProvider: DateProvider
) {
var viewsAutoInstrumentation: ViewsAutoInstrumentation?
viewsInstrumentation = RUMViewsHandler(dateProvider: dateProvider)
var viewsAutoInstrumentation: UIViewControllerSwizzler?
var userActionsAutoInstrumentation: UserActionsAutoInstrumentation?
var longTasks: LongTaskObserver?

do {
if let predicate = configuration.uiKitRUMViewsPredicate {
viewsAutoInstrumentation = try ViewsAutoInstrumentation(predicate: predicate, dateProvider: dateProvider)
viewsInstrumentation.predicate = predicate
viewsAutoInstrumentation = try UIViewControllerSwizzler(handler: viewsInstrumentation)
}

if let predicate = configuration.uiKitRUMUserActionsPredicate {
Expand All @@ -86,26 +66,24 @@ internal final class RUMInstrumentation: RUMCommandPublisher {
self.viewsAutoInstrumentation = viewsAutoInstrumentation
self.userActionsAutoInstrumentation = userActionsAutoInstrumentation
self.longTasks = longTasks
self.swiftUIViewInstrumentation = SwiftUIRUMViewsHandler(dateProvider: dateProvider)
}

func enable() {
viewsAutoInstrumentation?.enable()
viewsAutoInstrumentation?.swizzle()
userActionsAutoInstrumentation?.enable()
longTasks?.start()
}

func publish(to subscriber: RUMCommandSubscriber) {
viewsAutoInstrumentation?.handler.publish(to: subscriber)
viewsInstrumentation.publish(to: subscriber)
userActionsAutoInstrumentation?.handler.publish(to: subscriber)
longTasks?.publish(to: subscriber)
swiftUIViewInstrumentation.publish(to: subscriber)
}

#if DD_SDK_COMPILED_FOR_TESTING
/// Removes RUM instrumentation swizzlings and deinitializes this component.
func deinitialize() {
viewsAutoInstrumentation?.swizzler.unswizzle()
viewsAutoInstrumentation?.unswizzle()
userActionsAutoInstrumentation?.swizzler.unswizzle()
RUMInstrumentation.instance = nil
}
Expand Down
Loading

0 comments on commit fa81f82

Please sign in to comment.