티스토리 뷰

iOS

ModernRIBs_tutorial2 - 1

Peppo 2022. 1. 23. 23:17
728x90

ModernRIBs_tutorial2 

 

 

들어가기전에
RIBs, ModernRIBs 에서는 protocol을 굉장히 많이 사용합니다.
protocol이 뭔지 모르겠다면 ..

진실의 방으로

 


튜토리얼 2 부터 블로깅을 시작합니다. 

RIBs 개념, 튜토리얼 1 은 천천히 정리해서 올릴게요!

 

 

아래부터

RIB은  RIB (굵게 + 회색바탕),

메소드는 method (굵게 +기울기),

파일은  file (굵게 + 밑줄)로 표기하겠습니다.

 

 

목표

  • 하위 RIB과 상위 RIB의 소통 방식에 대한 이해
  • 상위 interactor가 실행 될때  하위 RIB 연결/ 분리
  • Viewless RIB을 만들어보기
    • 뷰가 필요없는 RIB이 분리될때 view 수정사항을 정리
  • 상위 RIB이 로드 될때 하위 RIB을 연결
    • RIB의 lifecycle을 이해하기 위함
  • RIB의 유닛테스트

 


프로젝트 구조

tutorial1 에선 Root LoggedOut 라는 2개의 RIBs를 구성해 봤습니다.

이번에는 LoggedIn, OffGame, TicTacToe 라는 3개의 RIBs를 추가해볼건데요. 

이 튜토리얼이 끝날때, 어플리케이션은 아래와 같은 계층 구조를 갖게 됩니다.

 

 

LoggedIn RIB은 View가 없습니다. OffGame,과 TicTacToe RIBs를 전환 해주는게 목적이거든요.

다른 RIBs 들은 각자 viewControllers와 화면에 보여줄 view를 포함하고 있습니다.

 

OffGame 은 플레이어들이 새 게임 시작할 수 있게 하고, "Start Game" 버튼 인터페이스를 포함합니다.

TicTacToe 은 게임 필드를 보여주고 플레이어들이 이동할 수 있게 해줄겁니다.

 


상위 RIB과의 전달

사용자가 플레이어 이름을 입력하고 "Login" 버튼을 탭한 후엔 "Start game"이 보여져야 합니다.

LoggedOut Root 에 login action을 알려야할거고, 그 다음엔 root 라우터가 LoggedOut 에서 LoggedIn 전환을 조절할 겁니다. 

이어서, viewless인 LoggedIn OffGame을 로드 할 거고 OffGame에 해당하는 부분이 스크린에 보여집니다.

 

Root LoggedOut의 상위 RIB으로, 이 router는 LoggedOut의 인터렉터로 구성됩니다.

이 listener 인터페이스를 통해 LoggedOut에서 발생된 login 이벤트를 Root 로 보내야 합니다.

 

먼저, LoggedOutListener 메소드를 추가해 플레이어가 로그인 상태라는걸 LoggedOut  Root 에 알려주게 합니다.

//  LoggedOutInteractor.swift

protocol LoggedOutListener: class {
    func didLogin(withPlayer1Name player1Name: String, player2Name: String)
}

 

이렇게 하면 LoggedOut의 상위 RIB이 didLogin 을 구현하게 하고 컴파일러가 상위 하위 RIB 간의 연결이 잘되어있는지 확인 합니다.

아래는 새로 선언된 listener를 호출을 하기위해 LoggedOutInteractor 안에 있는  login 메소드를 변경합니다.

//  LoggedOutInteractor.swift

func login(withPlayer1Name player1Name: String?, player2Name: String?) {
    let player1NameWithDefault = playerName(player1Name, withDefaultName: "Player 1")
    let player2NameWithDefault = playerName(player2Name, withDefaultName: "Player 2")
    listener?.didLogin(withPlayer1Name: player1NameWithDefault, player2Name: player2NameWithDefault) // 추가
}

 

이렇게 하면, 유저가 "Login" 버튼을 탭한 후에 LoggedOut 의 listener가 알림을 받게 될겁니다. 

 


Routing to LoggedIn 

여기서 구현해보고자 하는건 위에서 "Login" 버튼을 탭 했을때의 알림을 받았으니 액션을 처리하는 겁니다.

 

RootInteractor에 있는 RootRouting 프로토콜 부분에 routeToLoggedIn() 메소드를 추가해주세요.

	protocol RootRouting: ViewableRouting {
	  func routeToLoggedIn(withPlayer1Name player1Name: String, player2Name: String)    
	}

이렇게 하게 되면 RootRouting을 채택하고 있는 RootInteractor 클래스에서는 무조건 routeToLoggedIn()을 구현해야 합니다. 

이 함수는 didLogin() 함수 안에 구현할거에요.

 

    func didLogin(withPlayer1Name player1Name: String, player2Name: String) {
        router?.routeToLoggedIn(withPlayer1Name: player1Name, player2Name: player2Name)
    }

이제 Login 버튼을 터치하면 RootLoggedIn으로 라우팅할 수 있도록 된겁니다

 


유저가 로그인할 때 LoggedIn 붙이고 LoggedOut 떼기

 

RootRouter가 새로 만든 RIB을 attach 하기 위해서는 RootRouter가 해당 RIB을 build 할 수 있어야 합니다.

그러기 위해선 RootRouter내부에 LoggedInBuildable 을 추가해줍니다.

// RootRouter

init(interactor: RootInteractable,
     viewController: RootViewControllable,
     loggedOutBuilder: LoggedOutBuildable,
     loggedInBuilder: LoggedInBuildable) {    // 추가
    self.loggedOutBuilder = loggedOutBuilder
    self.loggedInBuilder = loggedInBuilder    // 추가
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
}

 

추가하면 에러가 발생할 건데 

아래 MARK: - Private부분에 이렇게 작성해 줍니다.

// RootRouter

private let loggedInBuilder: LoggedInBuildable

 

그다음, RootBuilder에 LoggedInBuilder 객체를 생성하여

RootRouter에 객체전달을 하기 위해 RootBuilderbuild() 메소드를 업데이트 해줍니다.

 

// RootBuilder

//  객체전달을 위해  build 메소드에 새로생긴RIB의 Builder 객체를 생성해서 주입할수 있도록 하기
//  객체 생성했으면 RootRouter로 이동

func build() -> LaunchRouting {
    let viewController = RootViewController()
    let component = RootComponent(dependency: dependency,
                                  rootViewController: viewController)
    let interactor = RootInteractor(presenter: viewController)

	// 객체 생성 부분
    let loggedOutBuilder = LoggedOutBuilder(dependency: component)
    let loggedInBuilder = LoggedInBuilder(dependency: component)
    // 객체 전달 부분
    return RootRouter(interactor: interactor,
                      viewController: viewController,
                      loggedOutBuilder: loggedOutBuilder,
                      loggedInBuilder: loggedInBuilder)
}

마지막 부분을 보면 짐작이 가듯이 RootRouter 부분에서 이제 LoggedInBuilder를 생성할 수 있게 됐습니다.

 

튜토리얼2에서 제일 중요한 기능인 RootRouter에서 LoggedOut을 detach하고 LoggedIn을 attach 하는 기능을 구현해 보겠습니다.

 

// RootRouter
// MARK: - RootRouting

func routeToLoggedIn(withPlayer1Name player1Name: String, player2Name: String) {
    // Detach LoggedOut RIB.
    if let loggedOut = self.loggedOut {
        detachChild(loggedOut)
        viewController.dismiss(viewController: loggedOut.viewControllable)
        self.loggedOut = nil
    }

    let loggedIn = loggedInBuilder.build(withListener: interactor)
    attachChild(loggedIn)
}

상위 RIB의 역할이 매우 중요해요.

 

RIBs 아키텍처에서 상위 router는 항상 하위 router들을 연결 합니다.

 

새로 생긴 LoggIn 으로부터 전달받은 이벤트를 구현해주기 위해,

RootInteractor가 LoggedIn의 listener로서 동작합니다. 

 

RootIneractable이 LoggedInBuilder build 메소드 listener로서, 

RootIneractable에 LoggedInListener 프로토콜 채택이 필요합니다.

// RootRouter

protocol RootInteractable: Interactable, LoggedOutListener, LoggedInListener {
    weak var router: RootRouting? { get set }
    weak var listener: RootListener? { get set }
}

 

하위 RIB이 viewController (뷰) 를 갖고 있다면, 하위RIB이 attach 되거나 detach 될때

상위 RIB은 present 또는 dismiss를 해줘야 합니다. 

// RootRouter

protocol RootViewControllable: ViewControllable {
    func present(viewController: ViewControllable)
    func dismiss(viewController: ViewControllable)
}

 

RootViewControllable 프로토콜에 dismiss() 메소드가 추가 되었으니,

해당 프로토콜과 연결된 RootViewControllerdismiss() 메소드를 구현해야겠죠!

이전에 present() 메소드 구현했던 부분 아래에 작성하면 좋을것 같습니다!

// RootViewController

func dismiss(viewController: ViewControllable) {
    if presentedViewController === viewController.uiviewController {
        dismiss(animated: true, completion: nil)
    }
}

 


 

LoggedInViewControllable  을 만드는 대신 넘기기 (pass)

 

LoggedIn 은 view가 없지만 하위 RIB의 뷰는 보여줄 수 있어야해서, 

LoggedIn의 상위 뷰 (Root)에 접근할 수 있게 해줘야 합니다. 

 

// RootViewController
// MARK: LoggedInViewControllable

extension RootViewController: LoggedInViewControllable {
}

지금은 채택만 시켜줍시다.

자세한 구현은 튜토리얼3에서 진행할게요. 


흐아.. 튜토리얼1 때와는 다르게 양이 너무 많네요..

일단 여기까지 하고 2-2 에서 마무리 짓겠습니다. 

728x90

'iOS' 카테고리의 다른 글

[Swift] 클로저 (Closure) (1)  (0) 2022.01.30
ModerRIBs_tutorial 2 - 2  (0) 2022.01.26
[Swift] 함수 (Functions) (2)  (0) 2022.01.21
[Swift] 함수 (Functions) (1)  (0) 2022.01.19
[Swift] 프로토콜 (Protocol)  (0) 2022.01.16