๊ฐ„๋‹จํ•œ ๊ตฌ์กฐ

์ผ๋‹จ RIB์˜ ๊ตฌ์กฐ๋Š” ์œ„์™€ ๊ฐ™๋‹ค.

  • ๋…ธ๋ž€์ƒ‰ ์ง์‚ฌ๊ฐํ˜•: RIB ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” Protocol
  • ๋นจ๊ฐ„์ƒ‰ ์ง์‚ฌ๊ฐํ˜•: RIB ์™ธ๋ถ€์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” Protocol
  • ๋‘ฅ๊ทผ ์ง์‚ฌ๊ฐํ˜•: Class

๋‘ฅ๊ทผ ์ง์‚ฌ๊ฐํ˜• (Router, Builder, Interactor, View) ๊ทผ์ฒ˜์— ์žˆ๋Š” Protocol์€ ํ•ด๋‹น ํŒŒ์ผ ๋‚ด์— ์ž‘์„ฑ๋˜๊ฒŒ ๋˜์–ด ๊ทผ์ฒ˜์— ๋ฐฐ์น˜ํ•˜์˜€๋‹ค. ์ด์ œ ์š”์†Œ ํ•˜๋‚˜ํ•˜๋‚˜์— ๋Œ€ํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•ด๋ณด๊ฒ ๋‹ค.

Builder

ํ•ด๋‹น RIB์ด ๊ฐ€์ง€๋Š” ์˜์กด์„ฑ์„ ๋ฐ›๊ณ , build() method๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ์ ์šฉ๋˜์–ด์•ผ ํ•˜๋Š” ์˜์กด์„ฑ์„ ๋ฐ›์˜ํ•˜๊ณ  ๋‚ด๋ถ€ Component๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•œ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์•ฑ์„ ๋งŒ๋“ค ๋•Œ, View tree๋ฅผ ๋”ฐ๋ผ์„œ ํ•ด๋‹น ๋กœ์ง๋“ค์ด ์œ„์น˜ํ–ˆ์—ˆ๋‹ค. ์ตœ์ข…์ ์œผ๋กœ ๋ณด์ด๋Š” ํ™”๋ฉด์€ ์ด์ „์— ๋ฐฉ๋ฌธํ–ˆ๋˜ View๋“ค์— ๋Œ€ํ•ด ์˜์กด์„ฑ์„ ๊ฐ–๊ณ ์„œ ๋งŒ๋“ค์–ด์ง€๊ฒŒ ๋œ๋‹ค. ์ด๋Ÿฐ ์˜์กด์„ฑ์„ ๋‹ด๋‹นํ•˜๋Š” ๊ฒƒ์ด Builder์ด๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์˜์กด์„ฑ์—๋Š” ์ •์  ์˜์กด์„ฑ, ๋™์  ์˜์กด์„ฑ ๋‘๊ฐ€์ง€ ๋ถ„๋ฅ˜๊ฐ€ ์กด์žฌํ•œ๋‹ค. ์ •์  ์˜์กด์„ฑ์€ ๋‹จ์ˆœํžˆ ํŠน์ • component๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ ์žˆ์–ด ์™ธ๋ถ€ ์š”์†Œ๋‚˜ ๊ณ„์‚ฐ๋œ ๊ฒฐ๊ณผ๊ฐ€ ํ•„์š”์—†์ด๋„ ๋งŒ๋“ค์–ด์งˆ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Aํ™”๋ฉด์—์„œ Bํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•˜๋Š”๋ฐ ์žˆ์–ด A๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ •๋ณด ๊ทธ๋Œ€๋กœ๋ฅผ ๋„ฃ์–ด์ฃผ๋Š” ๊ฒƒ์œผ๋กœ ๋๋‚œ๋‹ค๋ฉด ์ด ๊ฒฝ์šฐ ์ •์  ์˜์กด์„ฑ์„ ๊ฐ€์ง„๋‹ค ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿผ ๋™์  ์˜์กด์„ฑ์€ ๋ฌด์—‡์ผ๊นŒ? ํŠน์ • action์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค๋ฅธ component๊ฐ€ ์ƒ์„ฑ๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋งํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, A ํ™”๋ฉด์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ›๊ณ  ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Bํ™”๋ฉด์ด ๋งŒ๋“ค์–ด์ ธ์•ผ ํ•œ๋‹ค๋ฉด, ์ด๋Š” Aํ™”๋ฉด์— submit์ด ๋œ ์ดํ›„์— ํ•ด๋‹น ๊ฐ’์„ ๊ฐ™์ด ๋„ฃ์–ด์ฃผ์–ด B๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ๋™์  ์˜์กด์„ฑ์„ ๊ฐ€์ง„๋‹ค๋ผ ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

Builder์—์„œ๋Š” ์ •์  ์˜์กด์„ฑ์˜ ๊ฒฝ์šฐ component๋ฅผ ํ†ตํ•ด ๋„ฃ์–ด์ฃผ๊ณ , ๋™์  ์˜์กด์„ฑ์˜ ๊ฒฝ์šฐ build() ํ•จ์ˆ˜์˜ ์ธ์ˆ˜๋กœ ๋„ฃ์–ด์คŒ์œผ๋กœ์„œ ์ด๋ฅผ ๊ฐ€๋Šฅ์ผ€ํ•œ๋‹ค. build() ํ•จ์ˆ˜์—์„œ ์‹ค์ œ๋กœ RIB์˜ ๊ตฌ์„ฑ์š”์†Œ๊ฐ€ ๋ชจ๋‘ ์ƒ์„ฑ๋˜๊ณ  ์˜์กด์„ฑ์ด ์ฃผ์ž…๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด ์‹œ์ ์— ๋„ฃ์–ด์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Builder instance ์ž์ฒด๋Š” ์ถ”ํ›„ ์„ค๋ช…ํ•  Router๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์ž ๊น ์„ค๋ช…ํ•˜๋ฉด, Router๋Š” ์ •์  ์˜์กด์„ฑ์ด ์ฃผ์ž…๋œ Builder instance๋ฅผ ์ƒ์„ฑ์‹œ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€, Interactor๊ฐ€ ํ•˜์œ„ RIB์„ ์ƒ์„ฑํ•˜๋ผ๋Š” ๋ช…๋ น์„ ๋ฐ›๋Š” ์‹œ์ ์— ํ•˜์œ„ Builder์˜ build() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

Component

Builder์— ๋™์  ์˜์กด์„ฑ์„ ๋„ฃ์–ด์ฃผ๋Š” ์š”์†Œ์ด๋‹ค. ํ•˜์œ„ RIB์˜ Builder๋Š” self.dependency๋ฅผ ํ†ตํ•ด ์ƒ์œ„ RIB์˜ dependency์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. (์ •์  ์˜์กด์„ฑ) ํ•˜์ง€๋งŒ ํ•ด๋‹น Type์€ ํ•˜์œ„ RIB์—์„œ ์‚ฌ์šฉํ•˜๋Š” ChildDependency๋ผ๋Š” Protocol๋กœ interface๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ƒ์œ„ dependency ๊ตฌํ˜„์ฒด์— ๋ง‰ ์ ‘๊ทผํ•ด์„œ ์‚ฌ์šฉ์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ๋งŒ์•ฝ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ChildDependency์— ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•ด์•ผ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค.

// ์ƒ์œ„ RIB
protocol RootBuildable: Buildable {
    func build() -> LaunchRouting
}
 
final class RootBuilder: Builder<RootDependency>, RootBuildable {
 
    override init(dependency: RootDependency) {
        super.init(dependency: dependency)
    }
 
    func build() -> LaunchRouting {
        let viewController = RootViewController()
        let component = RootComponent(dependency: dependency,
                                      rootViewController: viewController)
        let interactor = RootInteractor(presenter: viewController)
 
        // ํ•˜์œ„ RIB์˜ Builder๋ฅผ ์ƒ์„ฑํ•  ๋•Œ, Builder๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” dependency๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.
        let loggedInBuilder = LoggedInBuilder(dependency: component) 
        
        // ์ƒ์œ„ RIB์˜ Router์—์„œ ํ•˜์œ„ RIB์˜ Builder๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
        let router = RootRouter(interactor: interactor,
                                viewController: viewController,
                                loggedInBuilder: loggedInBuilder) 
                                
 
        return (router, interactor)
    }
}
 
// ํ•˜์œ„ RIB: ์ดํ•ด๋ฅผ ๋•๊ธฐ ์œ„ํ•œ Protocol๊ณผ ์ƒ์„ฑ์ž๋งŒ ๊ฐ€์ ธ์˜ด, ์ž์„ธํ•œ ์‚ฌํ•ญ์€ Tutorial ์ง„ํ–‰
 
protocol LoggedInDependency: Dependency {
    var loggedInViewController: LoggedInViewControllable { get }
}
 
final class LoggedInBuilder: Builder<LoggedInDependency>, LoggedInBuildable {
 
    // ์ƒ์„ฑ๋  ๋•Œ ์ƒ์œ„ RIB์˜ dependency๋ฅผ ๋ฐ›์•˜์œผ๋‚˜, interface๊ฐ€ LoggedInDependency์ด๊ธฐ ๋•Œ๋ฌธ์— ์ ‘๊ทผ์ด ์ œํ•œ๋œ๋‹ค.
    override init(dependency: LoggedInDependency) {
        super.init(dependency: dependency)
    }
 
}

์ด๋ ‡๊ฒŒ ์ƒ์œ„ RIB์˜ dependency๋ฅผ Builder๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ๋งŒ์•ฝ ๋™์  ์˜์กด์„ฑ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด ์ด ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ๋ถ€์กฑํ•˜๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด Component์ด๋‹ค. ํ•˜์œ„ RIB์ด ๋งŒ๋“ค์–ด์งˆ ๋•Œ, ์ƒ์œ„ RIB์˜ Router์—์„œ instance๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ•˜์œ„ RIB์˜ build() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด ๋•Œ, ๋™์ ์œผ๋กœ ๋ฐœ์ƒํ•œ ๊ฐ’(์˜์กด์„ฑ)์„ build(player1, player2)์™€ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ ๋„ฃ์–ด์ค€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•˜์œ„ RIB์—์„œ๋Š” ํ•ด๋‹น ๊ฐ’์„ ๋ฐ›์•„ Component๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์˜์กด์„ฑ์„ ํ•ด๊ฒฐํ•œ RIB ๋‚ด๋ถ€ ์š”์†Œ๋ฅผ ๋งŒ๋“ ๋‹ค.

// ์ดํ•ด๋ฅผ ์œ„ํ•ด ๊ตฌ์ฒด ์‚ฌํ•ญ์€ ์ œ์™ธํ•˜๊ณ  ํ•ต์‹ฌ๋งŒ ๊ฐ€์ ธ์™”๋‹ค.
 
// ์ƒ์œ„ RIB์€ Interactor์˜ ์š”์ฒญ์— ๋”ฐ๋ผ Router์—์„œ ํ•˜์œ„ RIB์„ buildํ•œ๋‹ค.
final class RootRouter: LaunchRouter<RootInteractable, RootViewControllable>, RootRouting {
 
    func routeToLoggedIn(withPlayer1Name player1Name: String, player2Name: String) {
        
        // build์— ๋™์ ์œผ๋กœ ๋ฐ˜์˜๋˜์–ด์•ผ ํ•˜๋Š” ๊ฐ’์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋„ฃ์–ด ๋ณด๋‚ธ๋‹ค.
        let loggedIn = loggedInBuilder.build(withListener: interactor, 
                                             player1Name: player1Name, 
                                             player2Name: player2Name)
        attachChild(loggedIn.router)
    }
}
// MARK: - Builder
 
protocol LoggedInBuildable: Buildable {
    func build(withListener listener: LoggedInListener, player1Name: String, player2Name: String) -> (router: LoggedInRouting, actionableItem: LoggedInActionableItem)
}
 
final class LoggedInBuilder: Builder<LoggedInDependency>, LoggedInBuildable {
 
    func build(withListener listener: LoggedInListener, player1Name: String, player2Name: String) -> LoggedInRouting {
 
        // build ํ•จ์ˆ˜ ์•ˆ์—์„œ Component๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ƒˆ๋กœ์šด ์˜์กด์„ฑ ์š”์†Œ๋ฅผ ๋งŒ๋“ ๋‹ค.
        let component = LoggedInComponent(dependency: dependency,
                                          player1Name: player1Name,
                                          player2Name: player2Name)
        let interactor = LoggedInInteractor(games: component.games)
        interactor.listener = listener
 
        let offGameBuilder = OffGameBuilder(dependency: component)
        let router = LoggedInRouter(interactor: interactor,
                                    viewController: component.loggedInViewController,
                                    offGameBuilder: offGameBuilder)
        return (router, interactor)
    }
 
}

์‹ค์ œ Component๋Š” ์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ๋‹ค.

final class LoggedInComponent: Component<LoggedInDependency> {
 
    fileprivate var loggedInViewController: LoggedInViewControllable {
        return dependency.loggedInViewController
    }
 
    fileprivate var games: [Game] {
        return shared {
            return [RandomWinAdapter(dependency: self), TicTacToeAdapter(dependency: self)]
        }
    }
 
    internal let player1Name: String
    internal let player2Name: String
 
    init(dependency: LoggedInDependency, player1Name: String, player2Name: String) {
        self.player1Name = player1Name
        self.player2Name = player2Name
        super.init(dependency: dependency)
    }
}

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ์ ์€, component์˜ ๋ณ€์ˆ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ณ€์ˆ˜๋งŒ ์ ๊ณ , ์ ‘๊ทผ ์ œ์–ด์ž๋ฅผ ํ†ตํ•ด ๋ช…์‹œ์ ์œผ๋กœ ๋ณ€์ˆ˜ ์‚ฌ์šฉ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Interactor

Interactor๋Š” RIB์˜ Business logic์ด ๋‹ด๊ธฐ๋Š” ๊ณณ์ด๋‹ค. ์„ธ๊ฐœ์˜ Protocol์„ ๊ธฐ๋ณธ์œผ๋กœ ๊ฐ–๋Š”๋‹ค.

  1. Listener์˜ ๊ฒฝ์šฐ ์ƒ์œ„ RIB๊ณผ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•œ Interface์ด๋‹ค. ํ•ด๋‹น Listener๋Š” ์ƒ์œ„ RIB์˜ Interactor๊ฐ€ ์ฑ„ํƒํ•˜๊ณ  ์žˆ๋‹ค.
  2. Routing์˜ ๊ฒฝ์šฐ Router์— ์š”์ฒญํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ๋“ค์ด ๋‚˜์—ด๋˜์–ด ์žˆ๋‹ค.
  3. Presentable์˜ ๊ฒฝ์šฐ View์— ์š”์ฒญํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ๋“ค์ด ๋‚˜์—ด๋˜์–ด ์žˆ๋‹ค.

View

View๋Š” View์™€ View Controller ๋ชจ๋‘๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค. ๋‹ค๋ฅธ ์•„ํ‚คํ…์ณ ํŒจํ„ด์—์„œ ์ฒ˜๋Ÿผ View๋Š” ๋ฉ์ฒญํ•˜๋‹ค. ๋‹จ์ˆœํžˆ ๊ทธ๋ฆฌ๋Š” ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค. View์—์„œ ๋ณ€๊ฒฝ๋œ ์‚ฌํ•ญ์„ ์•Œ๋ฆฌ๊ธฐ ์œ„ํ•ด์„œ PresentableListner๋ฅผ ์ฑ„ํƒํ•œ ๊ณณ์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ธ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” Interactor๊ฐ€ ๊ทธ ์—ญํ• ์„ ํ•œ๋‹ค.

Router

Router๋Š” Interactor์˜ ์š”์ฒญ์„ ๋ฐ›์•„ RIB์„ Attach, Detachํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•œ๋‹ค. Interactable์€ Interactor๊ฐ€ ์ฑ„ํƒํ•˜๋Š” Protocol๋กœ, ํ•˜์œ„ RIB์˜ Interactor์˜ Listener, Router๊ฐ€ Interactor์— ์š”์ฒญํ•ด์•ผ ํ•˜๋Š” ์š”์†Œ๋“ค์„ ๋ชจ๋‘ ์ฑ„ํƒํ•˜๊ณ  ์žˆ๋‹ค.

ViewControllable์€ View ์š”์†Œ์˜ transition์— ๊ด€๋ จ๋œ ๊ฒƒ๋“ค์„ ์ •์˜ํ•œ Protocol์ด๋‹ค. RIB Attach, Detach์‹œ ๋ฐœ์ƒํ•˜๋Š” transition์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ํ•ด๋‹น Protocol์€ View๊ฐ€ ์ค€์ˆ˜ํ•˜๊ณ  ์žˆ๋‹ค.

protocol LoggedInInteractable: Interactable, OffGameListener, GameListener {
    var router: LoggedInRouting? { get set }
    var listener: LoggedInListener? { get set }
}
 
protocol LoggedInViewControllable: ViewControllable {
    func replaceModal(viewController: ViewControllable?)
}
 
final class LoggedInRouter: Router<LoggedInInteractable>, LoggedInRouting {
 
    // MARK: - LoggedInRouting
 
    func cleanupViews() {
        if currentChild != nil {
            viewController.replaceModal(viewController: nil)
        }
    }
 
    func routeToOffGame(with games: [Game]) {
        detachCurrentChild()
        attachOffGame(with: games)
    }
 
    func routeToGame(with gameBuilder: GameBuildable) {
        detachCurrentChild()
 
        let game = gameBuilder.build(withListener: interactor)
        self.currentChild = game
        attachChild(game)
        viewController.replaceModal(viewController: game.viewControllable)
    }
    ...
}