https://github.com/comeonyoh/NavigatableRIBs
UINavigationController, we typically invoke popViewController through an action event or simply by swiping back from left to right. This process automatically deallocates the topViewController of the UINavigationController, eliminating the need for manual memory management.detachChild method of the router cannot be called directly because the framework does not capture the pop swipe gesture. This limitation leads to a memory leak bug.viewDidAppear method of UIViewController for supporting pop swipe gesture.popViewController method.UITabbar button is clicked or the navigation back button’s long click occured.
popToRootViewController or popToViewController(_ viewController: UIViewController)First, We have to know the basic pars of a RIBs.

Pars of a RIBs.
Second, We have to know the life cycle of UINavigationController.

navigationController(_:didShow:animated:) is called, with the didShow parameter specifically indicating the UIViewController that was shown.As a result,

navigationController(_:didShow:animated:) is called, it is necessary to locate the RIB family(especially the Router) associated with the didShow UIViewController. Subsequently, we should invoke the detach method to remove child elements from the memory.As a code,
Create a common UINavigationController and set it to delegate to itself. We define a cachedViewController property to call detach method.
public class CommonRIBNavigationController: UINavigationController, UINavigationControllerDelegate {
private var cachedViewController: [UIViewController] = []
public override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
public override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)
if cachedViewController.contains(viewController) == false {
cachedViewController.append(viewController)
}
}
public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard cachedViewController.count > children.count else { return }
guard let currentIndex = cachedViewController.firstIndex(of: viewController), cachedViewController.count > currentIndex + 1 else { return }
// When the last dismissed view controller is kind of a RIBs and CommonRIBsViewController.
guard cachedViewController[currentIndex + 1] is ViewControllable, let current = cachedViewController[currentIndex] as? CommonRIBsViewController else {
cachedViewController = Array(cachedViewController[0...currentIndex])
return
}
current.resourceFlusher?.flushRIBsResources()
cachedViewController = Array(cachedViewController[0...currentIndex])
}
}
When thenavigationController(_:didShow:animated:) is called, we handle it based on whether it confirms to the RIBs framework.
Create a common UIViewController and Router(RIBs).
public protocol CommonRIBsInteractable: Interactable {
var resourceFlusher: CommonRIBsResourceFlush? { get }
}
public protocol CommonRIBsResourceFlush {
func flushRIBsResources()
}
public class CommonRIBsViewController: UIViewController, ViewControllable {
public var resourceFlusher: CommonRIBsResourceFlush? {
nil
}
}
public class CommonRIBsRouter<InteractorType, ViewControllerType>: ViewableRouter<InteractorType, ViewControllerType>, CommonRIBsResourceFlush {
public var nextScreenRouter: ViewableRouting?
/**
Easy short-cut push method to use same router.
*/
@discardableResult
public func push(nextRouter: ViewableRouting?, animated: Bool) -> Bool {
if nextScreenRouter != nil {
flushRIBsResources()
}
guard let next = nextRouter else { return false }
nextScreenRouter = next
nextScreenRouter?.interactable.activate()
nextScreenRouter?.load()
viewControllable.uiviewController.navigationController?.pushViewController(next.viewControllable.uiviewController, animated: animated)
return true
}
/**
Deactivate RIBs resource and set nil
*/
public func flushRIBsResources() {
nextScreenRouter?.interactable.deactivate()
nextScreenRouter = nil
}
}
Examples
final class MasterViewController: CommonRIBsViewController, MasterPresentable {
override var resourceFlusher: CommonRIBsResourceFlush? {
guard let listener = listener as? CommonRIBsInteractable else { return nil }
return listener.resourceFlusher
}
// ... Do other things...
}
protocol MasterInteractable: CommonRIBsInteractable {
// ... Do other things...
}
final class MasterInteractor: PresentableInteractor<MasterPresentable>, MasterInteractable {
public var resourceFlusher: CommonRIBsResourceFlush? {
router as? CommonRIBsResourceFlush
}
// ... Do other things...
}
final class MasterRouter: CommonRIBsRouter <MasterInteractable, ViewControllable>, MasterRouting, LaunchRouting {
// ... Do other things...
func routeToDetail(at case: MasterExampleCase) {
let detail = DetailBuilder(dependency: EmptyComponent()).build(`case`)
// Use common push method.`
push(nextRouter: detail, animated: true)
}
}