Skip to content

Commit

Permalink
Turn on experiment observersOnBackgroundQueue (#20)
Browse files Browse the repository at this point in the history
ViewControllerObserver methods are now called on the background queue.

Co-authored-by: Gleb Tarasov <[email protected]>
  • Loading branch information
pilot34 and Gleb Tarasov authored Dec 19, 2024
1 parent cb6d33b commit cd47111
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 129 deletions.
40 changes: 10 additions & 30 deletions PerformanceSuite/Sources/InstanceObservers/TTIObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,8 @@ final class TTIObserver<T: TTIMetricsReceiver>: ViewControllerInstanceObserver,
}
}
}
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
} else {
PerformanceMonitoring.queue.async(execute: action)
}
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
}

func beforeViewDidLoad() {
Expand All @@ -72,12 +68,8 @@ final class TTIObserver<T: TTIMetricsReceiver>: ViewControllerInstanceObserver,
self.screenCreatedTime = now
}
}
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
} else {
PerformanceMonitoring.queue.async(execute: action)
}
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
}

func afterViewWillAppear() {
Expand All @@ -104,12 +96,8 @@ final class TTIObserver<T: TTIMetricsReceiver>: ViewControllerInstanceObserver,
}
}

if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
} else {
PerformanceMonitoring.queue.async(execute: action)
}
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
}

func afterViewDidAppear() {
Expand All @@ -121,12 +109,8 @@ final class TTIObserver<T: TTIMetricsReceiver>: ViewControllerInstanceObserver,
}
}

if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
} else {
PerformanceMonitoring.queue.async(execute: action)
}
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
}

func beforeViewWillDisappear() {
Expand All @@ -138,12 +122,8 @@ final class TTIObserver<T: TTIMetricsReceiver>: ViewControllerInstanceObserver,
}
}

if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
} else {
PerformanceMonitoring.queue.async(execute: action)
}
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
action()
}

static var identifier: AnyObject {
Expand Down
20 changes: 6 additions & 14 deletions PerformanceSuite/Sources/Observers/LastOpenedScreenObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,14 @@ final class LastOpenedScreenObserver: ViewControllerObserver {
// MARK: - Top screen detection

private func rememberOpenedScreenIfNeeded(_ viewController: UIViewController) {
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
DispatchQueue.main.async {
guard self.isTopScreen(viewController) else {
return
}
PerformanceMonitoring.queue.async {
let description = RootViewIntrospection.shared.description(viewController: viewController)
AppInfoHolder.screenOpened(description)
}
}
} else {
guard isTopScreen(viewController) else {
DispatchQueue.main.async {
guard self.isTopScreen(viewController) else {
return
}
let description = RootViewIntrospection.shared.description(viewController: viewController)
AppInfoHolder.screenOpened(description)
PerformanceMonitoring.queue.async {
let description = RootViewIntrospection.shared.description(viewController: viewController)
AppInfoHolder.screenOpened(description)
}
}
}

Expand Down
87 changes: 20 additions & 67 deletions PerformanceSuite/Sources/Observers/ViewControllerObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,86 +58,54 @@ final class ViewControllerObserverFactory<T: ViewControllerInstanceObserver, S:
}

private func observer(for viewController: UIViewController) -> T? {
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))
} else {
precondition(Thread.isMainThread)
}
dispatchPrecondition(condition: .onQueue(PerformanceMonitoring.queue))

if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
if let observer = ViewControllerObserverFactoryHelper.existingObserver(for: viewController, identifier: T.identifier) as? T {
return observer
}
if let observer = ViewControllerObserverFactoryHelper.existingObserver(for: viewController, identifier: T.identifier) as? T {
return observer
}

guard let screen = metricsReceiver.screenIdentifier(for: viewController) else {
return nil
}

if !PerformanceMonitoring.experiments.observersOnBackgroundQueue {
if let observer = ViewControllerObserverFactoryHelper.existingObserver(for: viewController, identifier: T.identifier) as? T {
return observer
}
}

let tPointer = unsafeBitCast(T.identifier, to: UnsafeRawPointer.self)
let observer = observerMaker(screen)
objc_setAssociatedObject(viewController, tPointer, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return observer
}

func beforeInit(viewController: UIViewController) {
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.beforeInit()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
} else {
observer(for: viewController)?.beforeInit()
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.beforeInit()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
}

func beforeViewDidLoad(viewController: UIViewController) {
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.beforeViewDidLoad()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
} else {
observer(for: viewController)?.beforeViewDidLoad()
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.beforeViewDidLoad()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
}

func afterViewDidAppear(viewController: UIViewController) {
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.afterViewDidAppear()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
} else {
observer(for: viewController)?.afterViewDidAppear()
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.afterViewDidAppear()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
}

func beforeViewWillDisappear(viewController: UIViewController) {
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.beforeViewWillDisappear()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
} else {
observer(for: viewController)?.beforeViewWillDisappear()
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.beforeViewWillDisappear()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
}

func afterViewWillAppear(viewController: UIViewController) {
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.afterViewWillAppear()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
} else {
observer(for: viewController)?.afterViewWillAppear()
PerformanceMonitoring.queue.async {
self.observer(for: viewController)?.afterViewWillAppear()
self.ensureDeallocationOnTheMainThread(viewController: viewController)
}
}

Expand Down Expand Up @@ -196,23 +164,8 @@ class ViewControllerObserverCollection: ViewControllerObserver {
/// Non-generic helper for generic `ViewControllerObserverFactory`. To put all the static methods and vars there.
final class ViewControllerObserverFactoryHelper {
static func existingObserver(for viewController: UIViewController, identifier: AnyObject) -> Any? {
if PerformanceMonitoring.experiments.observersOnBackgroundQueue {
let tPointer = unsafeBitCast(identifier, to: UnsafeRawPointer.self)
if let result = objc_getAssociatedObject(viewController, tPointer) {
return result
}
} else {
var vc: UIViewController? = viewController
while let current = vc {
let tPointer = unsafeBitCast(identifier, to: UnsafeRawPointer.self)
if let result = objc_getAssociatedObject(current, tPointer) {
return result
}
vc = current.parent
}
}

return nil
let tPointer = unsafeBitCast(identifier, to: UnsafeRawPointer.self)
return objc_getAssociatedObject(viewController, tPointer)
}

static func existingObserver(forChild viewController: UIViewController, identifier: AnyObject) -> Any? {
Expand Down
8 changes: 1 addition & 7 deletions PerformanceSuite/Sources/Public/PerformanceMonitoring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ public protocol AppMetricsReceiver {}
protocol AppMetricsReporter: AnyObject {}

public struct Experiments {
public init(observersOnBackgroundQueue: Bool = false) {
self.observersOnBackgroundQueue = observersOnBackgroundQueue
}


/// Experiment to try to create view controller observers on the PerformanceMonitoring.queue
let observersOnBackgroundQueue: Bool
public init() {}
}

public enum PerformanceMonitoring {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public protocol ScreenMetricsReceiver: AnyObject {
/// This method should be as effective as possible. Slow implementation may harm app performance.
///
/// This method is called on the main thread only once, during `UIViewController` initialization.
/// If experiment `observersOnBackgroundQueue` is turned on, this method is called on the background internal queue `PerformanceMonitoring.queue`.
/// This method is called on the background internal queue `PerformanceMonitoring.queue`.
/// Slow implementation may harm overall performance and also can affect the precision of the measurements.
///
/// Default implementation will return nil for view controllers that are not from the main bundle and return `UIViewController` itself for others
Expand Down
5 changes: 1 addition & 4 deletions PerformanceSuite/Tests/TTIObserverExtensionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ class TTIObserverExtensionTests: XCTestCase {

override func setUpWithError() throws {
try super.setUpWithError()
PerformanceMonitoring.experiments = Experiments(observersOnBackgroundQueue: true)
defaultTimeProvider = timeProvider
metricsReceiver = TTIMetricsReceiverStub()
try PerformanceMonitoring.enable(config: [.screenLevelTTI(metricsReceiver)], experiments: Experiments(observersOnBackgroundQueue: true))
try PerformanceMonitoring.enable(config: [.screenLevelTTI(metricsReceiver)])
}

override func tearDownWithError() throws {
Expand All @@ -33,8 +32,6 @@ class TTIObserverExtensionTests: XCTestCase {
PerformanceMonitoring.consumerQueue.sync {}

try PerformanceMonitoring.disable()

PerformanceMonitoring.experiments = Experiments()
}

func testAllViewControllerMethodsAreCalledWhenMonitoringIsEnabled() {
Expand Down
6 changes: 0 additions & 6 deletions PerformanceSuite/Tests/ViewControllerObserverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,10 @@ import XCTest

class ViewControllerObserverTests: XCTestCase {

override func setUp() {
super.setUp()
PerformanceMonitoring.experiments = Experiments(observersOnBackgroundQueue: true)
}

override func tearDown() {
super.tearDown()
PerformanceMonitoring.consumerQueue.sync { }
PerformanceMonitoring.queue.sync { }
PerformanceMonitoring.experiments = Experiments()
}

func testObserversCollection() {
Expand Down

0 comments on commit cd47111

Please sign in to comment.