uber / uber/RIBs-iOS

Add more Routing lifecycle events to support RIB Tree tracing

Open
#24 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Swift
Stars
193
Forks
23
PR merge metrics
No merged PRs in 30d

Description

Original issue: https://github.com/uber/RIBs/issues/373

We are trying to improve the overall performance of our app. The first thing we think we should do beside of manual Profiling is tracking performance for specific code.

For UIView/UIViewController, swizzle help to automatically record a trace then send to a Telemetry tracing API. We also want to do that for Interactor/Router but they are pure Swift so no swizzle at all.

We tried to do that by adding more lifecycle events for Router and Interactor like below:

Router:

/// The lifecycle stages of a router scope.
public enum RouterLifecycle {

/// Router did load.
case didLoad

/// Router will attach child
case willAttachChild(Routing)

/// Router did attach child
case didAttachChild(Routing)

/// Router will detach child
case willDetachChild(Routing)

/// Router did detach child
case didDetachChild(Routing)

}

open class Router: Routing {
/// Attaches the given router as a child.
///
/// - parameter child: The child Router to attach.
public final func attachChild(_ child: Routing) {
assert(!(children.contains { $0 === child }), "Attempt to attach child: (child), which is already attached to (self).")
lifecycleSubject.onNext(.willAttachChild(child))

    children.append(child)
    
    // Activate child first before loading. Router usually attaches immutable children in didLoad.
    // We need to make sure the RIB is activated before letting it attach immutable children.
    child.interactable.activate()
    child.load()
    
    lifecycleSubject.onNext(.didAttachChild(child))
}

/// Detaches the given `Router` from the tree.
///
/// - parameter child: The child `Router` to detach.
public final func detachChild(_ child: Routing) {
    lifecycleSubject.onNext(.willDetachChild(child))

    child.interactable.deactivate()
    children.removeElementByReference(child)
    
    lifecycleSubject.onNext(.didDetachChild(child))
}

}
Then we can do recursive observe for child RIBs lifecycle to add performance trace. This enables as we can add observer only one in Root:

RootRouter:

func observeRouter(_ router: Routing) {
let didBecomeActive = router.interactable.isActiveStream.skip(1).filter { $0 == false }

let willAttachChild = router
    .lifecycle
    .compactMap { lifecycle -> Routing? in
        if case .willAttachChild(let child) = lifecycle {
            return child
        } else {
            return nil
        }
    }
    .takeUntil(didBecomeActive)

let didAttachChild = router
    .lifecycle
    .compactMap { lifecycle -> Routing? in
        if case .didAttachChild(let child) = lifecycle {
            return child
        } else {
            return nil
        }
    }
    .takeUntil(didBecomeActive)

_ = willAttachChild
    .subscribe(onNext: { child in            
        /// Recursive observe child router
        observeRouter(child)
    })

_ = willAttachChild
    .flatMap { child -> Observable<(Routing)> in
        return child.interactable.isActiveStream.filter { $0 }.map { _ in return child }
    }
    .subscribe(onNext: { child in
        /// Start Performance Tracing Interator didBecomeActive
    })

_ = didAttachChild
    .subscribe(onNext: { child in
        /// Stop Performance Tracing here
    })

}
We can simply add observer at the Root RIB and for example, use os_signpost to tracing performance in Instrument:

Screen Shot 2020-07-21 at 00 16 41

Adding more lifecycle event for Router also help to easier to implement a RIB Tree real-time viewer like https://github.com/srea/RIBsTreeViewerClient or https://github.com/imairi/RIBsTreeMaker

I really want to know your ideas and willing to open a PR if this will make sense to include in Uber RIBs

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Router attachChild and detachChild lifecycle paths and the RootRouter observeRouter example described in the issue. Review how Router and Interactor lifecycle state is currently exposed, then clarify the accepted event API and tracing behavior with maintainers. Done means the lifecycle additions and their intended observation use case are agreed and implemented.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
mobile-dev, observability
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.