uber / uber/RIBs

Add more Routing lifecycle events to support RIB Tree tracing

Open
#373 2 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

iOS
Dominant language
Kotlin
Stars
7.9k
Forks
914
PR merge metrics
No merged PRs in 30d

Description

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<InteractorType>: 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 and Interactor lifecycle proposal and the RootRouter observeRouter example, including the attachChild and detachChild entry points. Determine whether the additional lifecycle events and recursive observation fit RIBs; done requires an agreed API and implementation scope for tracing or real-time tree viewing.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.