RIBs 아키텍처 빠른 이해
이 문서는 작동하는 최소 샘플을 기준으로 RIBs의 핵심만 빠르게 익히도록 구성했습니다.
- 샘플 프로젝트: ../examples/ribs-sample/ribsSample.xcodeproj
- 권장 시작 파일: SceneDelegate.swift
1. 초등학생 버전으로 먼저 이해하기
RIBs는 "역할 나눠서 일하는 팀"입니다.
ViewController: 손님이 보는 가게 창구(화면)Interactor: 주문을 실제로 처리하는 주방(로직)Router: 손님을 어느 자리로 보낼지 정하는 안내자(화면 이동)Builder: 가게를 처음 세팅하는 사람(객체 조립)
즉, 한 사람이 다 하는 게 아니라 각자 맡은 일만 하니까 실수가 줄고 고치기 쉬워집니다.
2. 처음 보는 사람용 1분 요약
- 버튼을 누르면, 화면이 직접 처리하지 않고 Interactor에게 전달합니다.
- Interactor는 상태를 바꾸고 필요하면 상위 RIB에 이벤트를 올립니다.
- Router는 필요한 Child RIB를 붙이거나 떼면서 화면 구조를 관리합니다.
- Builder는 시작할 때 필요한 객체를 만들어 한 번에 연결합니다.
이 패턴의 핵심은 UI/로직/이동 책임 분리입니다.
3. RIBs를 왜 쓰나?
- 책임 분리: 화면(UI), 비즈니스 로직, 라우팅을 명확히 분리
- 확장성: 기능 단위를 RIB으로 쪼개서 유지보수 용이
- 테스트 용이성: Interactor 중심 테스트 작성이 쉬움
4. 6요소 한 줄 요약 (풀 구조)
Builder: 객체 생성/주입 조립 담당Component: RIB 의존성 컨테이너 담당Router: Child RIB attach/detach, 화면 전환 흐름 담당Interactor: 사용자 이벤트/상태 처리(비즈니스 로직)Presenter: Interactor 결과를 View 표시 형태로 변환ViewController: UI 표시와 사용자 입력 전달
5. 이번 샘플 구조
ribsSample/
RIBsCore/
RIBsCore.swift
Root/
RootBuilder.swift
RootComponent.swift
RootPresenter.swift
RootRouter.swift
RootInteractor.swift
RootViewController.swift
LoggedOut/
LoggedOutBuilder.swift
LoggedOutComponent.swift
LoggedOutPresenter.swift
LoggedOutRouter.swift
LoggedOutInteractor.swift
LoggedOutViewController.swift
LoggedIn/
LoggedInBuilder.swift
LoggedInComponent.swift
LoggedInPresenter.swift
LoggedInRouter.swift
LoggedInInteractor.swift
LoggedInViewController.swift
SceneDelegate → RootBuilder.build() → RootRouter.launch() → LoggedOutBuilder.build()로그인 버튼 탭 시:
LoggedOutViewController → LoggedOutPresenter → LoggedOutInteractor → RootInteractor → RootRouter (LoggedIn attach)로그아웃 버튼 탭 시:
LoggedInViewController → LoggedInPresenter → LoggedInInteractor → RootInteractor → RootRouter (LoggedOut attach)
6. 핵심 코드 포인트
파일 읽기 순서
1-1 SceneDelegate → 1-2 RootBuilder → 1-3 RootComponent → 1-4 RootRouter → 1-5 RootInteractor → 1-6 RootPresenter → 1-7 RootViewController
2-1 LoggedOutBuilder → 2-2 LoggedOutComponent → 2-3 LoggedOutViewController → 2-4 LoggedOutPresenter → 2-5 LoggedOutInteractor → 2-6 LoggedOutRouter
3-1 LoggedInComponent → 3-2 LoggedInViewController → 3-3 LoggedInPresenter → 3-4 LoggedInInteractor → 3-5 LoggedInRouter → 3-6 LoggedInBuilder
A) 조립 시작점
RootBuilder.swift에서 Root RIB을 만들고, Scene에서 launch 합니다.
B) 이벤트 전달
LoggedOutViewController.swift는 버튼 입력을 Presenter로 전달합니다.
C) 상태 처리
LoggedOutInteractor.swift는 상태를 계산하고 Presenter/Root에 이벤트를 전달합니다.
D) 상위 라우팅 반영
RootRouter.swift는 로그인 시 LoggedOut를 detach하고 LoggedIn을 attach하며, 로그아웃 시 반대로 복귀시킵니다.
7. 앱 실행부터 Login Action까지 시퀀스 다이어그램
아래는 실제 샘플 코드 기준 Mermaid 시퀀스 다이어그램입니다.
iOS Runtime, SceneDelegate, RootBuilder, RootComponent, RootRouter, RootInteractor, RootPresenter, RootViewController, LoggedOutBuilder, LoggedOutComponent, LoggedOutPresenter, LoggedOutInteractor, LoggedOutViewController, LoggedInBuilder, LoggedInComponent, LoggedInPresenter, LoggedInInteractor, LoggedInViewController
sequenceDiagram
autonumber
actor User as 사용자
participant iOS as iOS Runtime
participant SD as SceneDelegate
participant RB as RootBuilder
participant RC as RootComponent
participant RR as RootRouter
participant RI as RootInteractor
participant RP as RootPresenter
participant RVC as RootViewController
participant LOB as LoggedOutBuilder
participant LOC as LoggedOutComponent
participant LOP as LoggedOutPresenter
participant LOI as LoggedOutInteractor
participant LOVC as LoggedOutViewController
participant LIB as LoggedInBuilder
participant LIC as LoggedInComponent
participant LIP as LoggedInPresenter
participant LII as LoggedInInteractor
participant LIVC as LoggedInViewController
iOS->>SD: scene(_:willConnectTo:options:)
SD->>RB: build()
RB->>RC: RootComponent 생성
RB->>RVC: RootViewController 생성
RB->>RP: RootPresenter(view) 생성
RB->>RI: RootInteractor(presenter) 생성
RB->>LOB: LoggedOutBuilder(dependency: RC) 생성
RB->>RR: RootRouter 생성
RB-->>SD: (router, viewController) 반환
SD->>SD: UIWindow 설정 / makeKeyAndVisible()
SD->>RR: launch()
RR->>RI: interactor.listener = self
RR->>RI: activate()
RI->>RP: presentLoggedOutState()
RP->>RVC: showStatus("State: LoggedOut")
RR->>LOB: build(listener: RI)
LOB->>LOC: LoggedOutComponent 생성
LOB->>LOVC: LoggedOutViewController 생성
LOB->>LOP: LoggedOutPresenter(view) 생성
LOB->>LOI: LoggedOutInteractor(presenter, initialUserName) 생성
LOB->>LOP: presenter.listener = interactor
LOB->>LOI: interactor.listener = RootInteractor
LOB->>LOI: activate()
LOI->>LOP: presentInitialState(initialUserName)
LOP->>LOVC: show(title) + setUserName(...)
LOB-->>RR: LoggedOutRouter 반환
RR->>RR: attachChild(loggedOutRouter)
RR->>RVC: set(content: LOVC)
Note over RVC: addChild -> addSubview -> constraints -> didMove
User->>LOVC: Login Action 버튼 탭
LOVC->>LOP: didTapLoginButton(userName)
LOP->>LOI: loggedOutPresenterDidTapLoginButton(userName)
LOI->>LOP: presentWelcome(userName)
LOP->>LOVC: show(title: "Welcome, ...")
LOI->>RI: loggedOutDidRequestLogin(userName)
RI->>RP: presentLoggedInState(userName)
RP->>RVC: showStatus("State: LoggedIn as ...")
RI->>RR: rootDidLogin(userName)
RR->>RR: detachChild(loggedOutRouter)
RR->>LIB: build(listener: RI, userName)
LIB->>LIC: LoggedInComponent 생성
LIB->>LIVC: LoggedInViewController 생성
LIB->>LIP: LoggedInPresenter(view) 생성
LIB->>LII: LoggedInInteractor(presenter, userName) 생성
LIB->>LIP: presenter.listener = interactor
LIB->>LII: interactor.listener = RootInteractor
LIB->>LII: activate()
LII->>LIP: presentLoggedIn(userName, appName)
LIP->>LIVC: show(title/detail)
RR->>RR: attachChild(loggedInRouter)
RR->>RVC: set(content: LIVC)
User->>LIVC: Logout 버튼 탭
LIVC->>LIP: didTapLogoutButton()
LIP->>LII: loggedInPresenterDidTapLogout()
LII->>RI: loggedInDidRequestLogout()
RI->>RP: presentLoggedOutState()
RP->>RVC: showStatus("State: LoggedOut")
RI->>RR: rootDidLogout()
RR->>RR: detachChild(loggedInRouter)
RR->>LOB: build(listener: RI)
RR->>RVC: set(content: LOVC)
시각 타임라인
SceneDelegate.scene(...)SceneDelegate → RootBuilder.build() 호출RootComponent + RootPresenter + RootRouter + RootInteractor + RootViewControllerRootRouter.launch()RootRouter가 LoggedOutBuilder.build(listener: RootInteractor) 실행LoggedOutInteractor.activate() → LoggedOutPresenter → 화면 초기 표시LoggedOutViewController.didTapButton()LoggedOutViewController → LoggedOutPresenter → LoggedOutInteractor → RootInteractorRootRouter가 LoggedIn을 attach하고 화면 전환LoggedOut으로 복귀초보자용 단계 설명
- 앱이 켜지면
SceneDelegate가 시작점이 됩니다. RootBuilder가 Root 화면/로직/라우터를 한 번에 조립합니다.RootRouter.launch()가LoggedOutRIB를 붙입니다.- 화면의 Login 버튼을 누르면
LoggedOutViewController가 이벤트를LoggedOutPresenter로 전달합니다. LoggedOutPresenter는 이벤트를LoggedOutInteractor로 전달하고, Interactor는 상위RootInteractor에 로그인 의도를 알립니다.- 이후
RootRouter가LoggedInRIB를 attach하고 화면을 전환합니다. LoggedIn화면에서 Logout을 누르면 다시LoggedOutRIB로 복귀합니다.
8. 빌드 방법
xcodebuild \
-project examples/ribs-sample/ribsSample.xcodeproj \
-scheme ribsSample \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath /private/tmp/ribsSampleDerived \
build
로컬 환경에 따라 Simulator 서비스 오류가 나면 Xcode에서 직접 실행해도 됩니다.
9. 다음 학습 순서
LoggedOut다음에LoggedInRIB 추가Router에서 attach/detach로 화면 전환 구현- Interactor 단위 테스트 작성으로 로직 고립 검증
10. 이번 대화 핵심 정리
A) 하위 전환 vs 상위 이벤트
- 하위(Child) 붙이기/떼기: 보통
Router가 담당 - 상위(Parent)로 이벤트 올리기: 보통
Interactor.listener체인으로 전달
B) Root와 Non-root Listener 차이
- Root RIB:
RootInteractor.listener = RootRouter패턴 가능 - Non-root RIB:
ChildInteractor.listener = ParentInteractor가 일반적
C) Presenter와 ViewController 의존 방향
Presenter -> ViewController: 화면 표시를 위해 필요ViewController -> Interactor/Router: 직접 의존하지 않고, 보통listener프로토콜만 참조- 입력 흐름:
ViewController -> Presenter -> Interactor - 출력 흐름:
Interactor -> Presenter -> ViewController
D) Root 비대화 방지 원칙
- Root는 오케스트레이션(큰 분기)만 담당
- 도메인별 Parent RIB(AuthRoot/AppRoot 등)로 분리
- 세부 판단(세션/권한/플래그)은 Service/UseCase로 분리
- Listener 프로토콜을 도메인별로 쪼개 결합도 축소