튜토리얼에 앞서 RIBs 프로젝트를 다운받고, 터미널에서 RIBs/ios/tooling 경로로 이동한다. 해당 경로에서
./install-xcode-template.sh
를 입력하여 템플릿을 설치한다.
템플릿에 맞게 Router, ViewController, Builder, Interactor가 생성된다.
https://github.com/uber/RIBs/tree/main/ios/tutorials/tutorial1 과정대로 진행하여, TicTacToe.xcworkspace를 생성한다.
RIB이 상호작용하고 통신하는 방식 이해
// RootRouter.swift
import RIBs
protocol RootInteractable: Interactable, LoggedOutListener {
var router: RootRouting? { get set }
var listener: RootListener? { get set }
}
protocol RootViewControllable: ViewControllable {
func present(viewController: ViewControllable)
}
final class RootRouter: LaunchRouter<RootInteractable, RootViewControllable>, RootRouting {
init(interactor: RootInteractable,
viewController: RootViewControllable,
loggedOutBuilder: LoggedOutBuildable) {
self.loggedOutBuilder = loggedOutBuilder
super.init(interactor: interactor, viewController: viewController)
interactor.router = self
}
override func didLoad() {
super.didLoad()
let loggedOut = loggedOutBuilder.build(withListener: interactor)
self.loggedOut = loggedOut
attachChild(loggedOut)
viewController.present(viewController: loggedOut.viewControllable)
}
// MARK: - Private
private let loggedOutBuilder: LoggedOutBuildable
private var loggedOut: ViewableRouting?
}
TicTacToe 프로젝트에는 LoggedOut RIB 구현이 되어있지 않은데, 실제로 구현해보자.
템플릿을 활용해 LoggedOut RIB을 생성한다.
protocol LoggedOutBuildable: Buildable {
func build(withListener listener: LoggedOutListener) -> LoggedOutRouting
}
final class LoggedOutBuilder: Builder<LoggedOutDependency>, LoggedOutBuildable {
override init(dependency: LoggedOutDependency) {
super.init(dependency: dependency)
}
func build(withListener listener: LoggedOutListener) -> LoggedOutRouting {
let component = LoggedOutComponent(dependency: dependency)
let viewController = LoggedOutViewController()
let interactor = LoggedOutInteractor(presenter: viewController)
interactor.listener = listener
return LoggedOutRouter(interactor: interactor, viewController: viewController)
}
}
protocol LoggedOutRouting: ViewableRouting {
// TODO: Declare methods the interactor can invoke to manage sub-tree via the router.
}
protocol LoggedOutPresentable: Presentable {
var listener: LoggedOutPresentableListener? { get set }
// TODO: Declare methods the interactor can invoke the presenter to present data.
}
protocol LoggedOutListener: AnyObject {
// TODO: Declare methods the interactor can invoke to communicate with other RIBs.
}
final class LoggedOutInteractor: PresentableInteractor<LoggedOutPresentable>, LoggedOutInteractable, LoggedOutPresentableListener {
weak var router: LoggedOutRouting?
weak var listener: LoggedOutListener?
// TODO: Add additional dependencies to constructor. Do not perform any logic
// in constructor.
override init(presenter: LoggedOutPresentable) {
super.init(presenter: presenter)
presenter.listener = self
}
override func didBecomeActive() {
super.didBecomeActive()
// TODO: Implement business logic here.
}
override func willResignActive() {
super.willResignActive()
// TODO: Pause any business logic.
}
}
protocol LoggedOutInteractable: Interactable {
var router: LoggedOutRouting? { get set }
var listener: LoggedOutListener? { get set }
}
protocol LoggedOutViewControllable: ViewControllable {
// TODO: Declare methods the router invokes to manipulate the view hierarchy.
}
final class LoggedOutRouter: ViewableRouter<LoggedOutInteractable, LoggedOutViewControllable>, LoggedOutRouting {
// TODO: Constructor inject child builder protocols to allow building children.
override init(interactor: LoggedOutInteractable, viewController: LoggedOutViewControllable) {
super.init(interactor: interactor, viewController: viewController)
interactor.router = self
}
}
위와 같은 UI 구성은 완료된 상태에서
로그인 버튼이 눌렸을 때,
LoginViewController가 listener(=LoggedOutPresentableListener, 즉, Interactor)에게 사용자가 로그인을 시도한다는 것을 알리고, 두명의 플레이어 이름을 전달하는 로직을 구현하려고 한다.
해당 로직을 구현하기 위해서 먼저 LoggedOutPresentableListener 프로토콜에 함수 하나를 선언하자.
protocol LoggedOutPresentableListener: class {
func login(withPlayer1Name player1Name: String?, player2Name: String?)
}
그리고 LoggedOutInteractor에 아래의 메소드를 추가한다.
// MARK: - LoggedOutPresentableListener
func login(withPlayer1Name player1Name: String?, player2Name: String?) {
let player1NameWithDefault = playerName(player1Name, withDefaultName: "Player 1")
let player2NameWithDefault = playerName(player2Name, withDefaultName: "Player 2")
print("\(player1NameWithDefault) vs \(player2NameWithDefault)")
}
private func playerName(_ name: String?, withDefaultName defaultName: String) -> String {
if let name = name {
return name.isEmpty ? defaultName : name
} else {
return defaultName
}
}
마지막으로, viewController에서 listener(=Interactor) 메소드를 호출할 수 있도록 연결해준다.
@objc private func didTapLoginButton() {
listener?.login(withPlayer1Name: player1Field?.text, player2Name: player2Field?.text)
}