Mobile Development 21 min read

Understanding RIB Architecture in iOS: A Complete Guide to Router‑Interactor‑Builder

This article explains the RIB (Router‑Interactor‑Builder) architecture for iOS, detailing its core components, how to generate and connect them with Xcode templates, manage navigation, inject dependencies, write unit tests with mock generation, and integrate the whole structure into a root RIB for a production‑ready app.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding RIB Architecture in iOS: A Complete Guide to Router‑Interactor‑Builder

What Is a RIB?

RIB stands for Router + Interactor + Builder. It is an iOS architecture that drives an application from business logic rather than view hierarchy. A RIB is visualized as a tree where each node can have zero or many child nodes, which can be attached or detached during the app lifecycle.

Diving Into RIBs

When creating a new RIB you can use the Xcode RIB template. Selecting RIB from the file creation menu generates four files: Builder, Interactor, Router, and View/Presenter.

LoginBuilder

import RIBs

protocol LoginDependency: Dependency { }

final class LoginComponent: Component<LoginDependency> { }

// MARK: - Builder
protocol LoginBuildable: Buildable {
    func build(withListener listener: LoginListener) -> LoginRouting
}

final class LoginBuilder: Builder<LoginDependency>, LoginBuildable {
    override init(dependency: LoginDependency) {
        super.init(dependency: dependency)
    }
    func build(withListener listener: LoginListener) -> LoginRouting {
        let component = LoginComponent(dependency: dependency)
        let viewController = LoginViewController()
        let interactor = LoginInteractor(presenter: viewController)
        interactor.listener = listener
        return LoginRouter(interactor: interactor, viewController: viewController)
    }
}

LoginInteractor

import RIBs
import RxSwift

protocol LoginRouting: ViewableRouting { }
protocol LoginPresentable: Presentable {
    var listener: LoginPresentableListener? { get set }
    func showActivityIndicator(_ isLoading: Bool)
}
protocol LoginListener: class { }

final class LoginInteractor: PresentableInteractor<LoginPresentable>, LoginInteractable, LoginPresentableListener {
    weak var router: LoginRouting?
    weak var listener: LoginListener?
    override init(presenter: LoginPresentable) {
        super.init(presenter: presenter)
        presenter.listener = self
    }
    override func didBecomeActive() { super.didBecomeActive() }
    override func willResignActive() { super.willResignActive() }
    // Business‑logic methods will be added here
}

LoginRouter

import RIBs

protocol LoginInteractable: Interactable {
    var router: LoginRouting? { get set }
    var listener: LoginListener? { get set }
}
protocol LoginViewControllable: ViewControllable { }

final class LoginRouter: ViewableRouter<LoginInteractable, LoginViewControllable>, LoginRouting {
    override init(interactor: LoginInteractable, viewController: LoginViewControllable) {
        super.init(interactor: interactor, viewController: viewController)
        interactor.router = self
    }
    // Navigation methods will be added here
}

LoginPresenter / LoginViewController

import RIBs
import RxSwift
import UIKit

protocol LoginPresentableListener: class { }

final class LoginViewController: UIViewController, LoginPresentable, LoginViewControllable {
    weak var listener: LoginPresentableListener?
    func showActivityIndicator(_ isLoading: Bool) { /* update UI */ }
    func present(_ viewController: ViewControllable) {
        present(viewController.uiviewController, completion: nil)
    }
}

The Interactor communicates with the Presenter (ViewController) to update UI, with the Router to navigate, and with the parent RIB via the Listener.

Advanced RIB Topics

Mock Generation

Using tools like Mockolo, you can annotate protocols with /// @mockable and generate mock implementations automatically. Example mock for WebServicing:

class WebServicingMock: WebServicing {
    init() { }
    var loginCallCount = 0
    var loginHandler: ((String, String, (Result<String, Error>) -> Void) -> ())?
    func login(username: String, password: String, handler: (Result<String, Error>) -> Void) {
        loginCallCount += 1
        loginHandler?(username, password, handler)
    }
}

Unit Testing

With generated mocks you can fully test Interactor and Router logic. Below is a simplified XCTest case for LoginInteractor covering activity‑indicator handling, web‑service calls, success and failure flows.

final class LoginInteractorTests: XCTestCase {
    private var interactor: LoginInteractor!
    private var presenter = LoginPresentableMock()
    private var listener = LoginListenerMock()
    private var router = LoginRoutingMock()
    private let webService = WebServicingMock()
    override func setUp() {
        super.setUp()
        interactor = LoginInteractor(presenter: presenter, webService: webService)
        interactor.router = router
        interactor.listener = listener
    }
    func test_didTapLogin_triggersWebTask_andShowsActivity() {
        presenter.showActivityIndicatorHandler = { isLoading in
            XCTAssertTrue(isLoading)
        }
        interactor.didTapLogin(username: "user", password: "pass")
        XCTAssertEqual(webService.loginCallCount, 1)
        XCTAssertEqual(presenter.showActivityIndicatorCallCount, 1)
    }
    func test_loginSuccess_callsDismissListener() {
        webService.loginHandler = { _, _, handler in handler(.success("uid")) }
        interactor.didTapLogin(username: "user", password: "pass")
        XCTAssertEqual(listener.dismissLoginFlowCallCount, 1)
        XCTAssertEqual(presenter.showActivityIndicatorCallCount, 2) // start & stop
    }
    func test_loginFailure_showsErrorAlert() {
        webService.loginHandler = { _, _, handler in handler(.failure(WebServiceError.generic)) }
        interactor.didTapLogin(username: "user", password: "pass")
        XCTAssertEqual(presenter.showErrorAlertCallCount, 1)
        XCTAssertEqual(presenter.showActivityIndicatorCallCount, 2)
    }
}

Dependency Injection

The sample uses Uber’s Needle framework to wire dependencies from a top‑level AppComponent down to each RIB, reducing boilerplate while keeping the protocol‑based design.

Conclusion

The RIB architecture separates concerns into Router, Interactor, Builder, View, and Presenter, enabling clear navigation, testability, and scalability for iOS apps. Combined with mock generation and a DI framework like Needle, it provides a robust foundation for large‑scale mobile projects.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Mobile DevelopmentiOStestingdependency-injectionRIB
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.