관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 6-3) RIBs 구조 Todo 앱 만들기 (Rx stream) 본문

Architecture (swift)/RIBs

[iOS - swift] 6-3) RIBs 구조 Todo 앱 만들기 (Rx stream)

jake-kim 2021. 4. 30. 23:23

0) 프로젝트 초기 세팅

1) Todo 앱 구조, LoggedOut RIB

2) Todo 앱 구조 LoggedIn RIB

3) Todo 앱 구조 Rx stream

이전까지 구현된 점

* 정리 - child RIB 생성, DI, attach/detach

  • Child RIB 생성
    • Parent Router에 Builder 프로퍼티 추가
    • viewless 경우: (p)ChildDependency ChildViewController 지정되어 있고, ParentComponent VC객체 선언과 init생성자 적용하여 값을 주입 (viewless하위에 viewful Child들이 생기는 것을 고려하기위함)
  • Child 의존성 정의
    • ChildDependency, ChildComponent, parentComponent+childDependency
    • 각 ViewController, Interactor의 생성자에 필요한 파라미터를 정의하여, build()함수 내에서 주입받아 가져가는 방식
    • 만약 parentComponent+childDependency로 넣어주지 않은 경우,
      build()에 파라미터 추가:  ChildBuildable의 build() 수정
  • attach
    • build()를 통해 child Routing객체 생성, 전역변수에 ViewableRouting? 선언 (추후에 detach하기위함)
    • attach, present
  • detach 주의사항: viewless인 RIB이 해제되는 경우
    • viewless RIB 해제되는 경우, Child 화면이 남아있는 형태일 있으므로, ViewlessInRouter의 cleanupViews() 부분 구현
      : 템플릿에서 자동으로 interactor의 willResignActive()에서 해당 함수가 불려지고 있는 형태

구현할점

  • detail contents RIB 생성: todoList에서 메모를 작성하면, detail contents 화면으로 이동되는 것
  • attach
  • TodoList RIB에서 Rx stream으로 데이터 상향, 하향 전달 -> LoggedIn -> DetailContents

detail contents RIB 생성

  • 생성 코드
  • 화면 flow: todoList에 목록 추가 -> Listener interface로 LoggedIn 접근 -> LoggedInInterface에서 routeToDetailContents

 

Rx stream

  • 데이터 전달: TodoList의 Listener Interface -> LoggedIn에 전달 -> Rx Stream -> OffGame에 전달
  • Rx stream에 사용될 MemoList 모델 정의
  • Rx Stream을 LoggedIn의 Component에 저장
    • LoggedInInteractor에서 stream객체를 받을 준비: private let mutableMemoStream선언, init에 적용
    • LoggedInBuilder의 LoggedInComponent에 shared로 stream 객체 선언, build()함수에서 interactor쪽에 생성자안에 전달
    • 코드
  • Rx Stream을 DetailContents에 주입
    • DetailContent의 Dependency, LoggedInComponent+DetailContentDependency, Component에 정의
    • DetailContentInteractor에 private let memoStream 추가, init적용
    • 코드
  • emit
    • TodoList의 Listener -> LoggedIn에 전달 -> LoggedInInteractor에서 emit -> LoggedInRouter에서 화면이동
    • 코드
  • subscribe: DetailContentsInteractor에서 수행

* 정리 - rx stream

  • Rx Stream 데이터형을 따로 정의
  • Parent
    • ParentComponent에 위 stream 데이터를 shared 싱글톤으로 선언
    • build() 함수에서 interactor쪽 생성자에 전달
  • Child
    • ChildDependency, ParentComponent+ChildDependency, ChildComponent 순서대로 적용
    • ChildInteractor에 private let myStream 추가, init 적용
  • emit & subscribe
    • emit은 ParentInteractor에서 실행
    • subscribe는 ChildInteractor에서 수행

 

cf) RIBs에서 navigation controller 사용 방법

- 출처: RIBs Issue

  • UINavigationController에 ViewControllable 성격 적용
extension UINavigationController: ViewControllable {
    public var uiviewController: UIViewController { return self }
    
    public convenience init(root: ViewControllable) {
        self.init(rootViewController: root.uiviewController)
    }
}
  • navigationController로 modal
// -Router.swift

func routeToAnotherRIB(with param: String) {
    detachCurrentChild()

    let rib = anotherRIBBuilder.build(withListener: interactor, parameter: param)
    self.currentChild = rib
    attachChild(rib)
        
    let navController = UINavigationController(root: rib.viewControllable)
        
    viewController.replaceModal(viewController: navController)
}

// RootViewController.swift

    // MARK: - RootViewControllable

    func replaceModal(viewController: ViewControllable?) {
        targetViewController = viewController

        guard !animationInProgress else {
            return
        }

        if presentedViewController != nil {
            animationInProgress = true
            dismiss(animated: true) { [weak self] in
                if self?.targetViewController != nil {
                    self?.presentTargetViewController()
                } else {
                    self?.animationInProgress = false
                }
            }
        } else {
            presentTargetViewController()
        }
    }
    
        // MARK: - Private

    private var targetViewController: ViewControllable?
    private var animationInProgress = false

    private func presentTargetViewController() {
        if let targetViewController = targetViewController {
            animationInProgress = true
            present(targetViewController.uiviewController, animated: true) { [weak self] in
                self?.animationInProgress = false
            }
        }
    }

* RIB사용 총 정리

[Child 생성]
  • Child RIB 생성
    • Parent Router에 Builder 프로퍼티 추가
    • viewless인 경우: (p)ChildDependency에 ChildViewController가 지정되어 있고, ParentComponent에 VC객체 선언과 init생성자 적용하여 이 값을 주입 (viewless하위에 viewful Child들이 생기는 것을 고려하기위함)
  • Child 의존성 정의
    • ChildDependency, ChildComponent, parentComponent+childDependency
    • 각 ViewController, Interactor의 생성자에 필요한 파라미터를 정의하여, build()함수에서 주입받아 가져가는 방식
    • 만약 parentComponent+childDependency로 넣어주지 않은 경우, build()에 파라미터 추가: ChildBuildable의 build() 수정
  • attach
    • build()를 통해 child Routing객체 생성, 전역변수에 ViewableRouting? 선언 (추후에 detach하기위함)
    • attach, present
  • detach 주의사항: viewless인 RIB이 해제되는 경우
    • viewless RIB이 해제되는 경우, Child의 화면이 남아있는 형태일 수 있으므로, ViewlessInRouter의 cleanupViews() 부분 구현: 템플릿에서 자동으로 interactor의 willResignActive()에서 해당 함수가 불려지고 있는 형태

[DI]
  • parent + child관계: childBuilder에 있는 Dependency 프로토콜에 명시 -> parentComponent + child 파일에서 주입
  • sibiling: Component 넣고, init구문에서 인수로 받도록 설정


[Rx Stream]
  • Rx Stream 데이터형을 따로 정의
  • Parent
    • ParentComponent에 위 stream 데이터를 shared 싱글톤으로 선언
    • build() 함수에서 interactor쪽 생성자에 전달
  • Child
    • ChildDependency, ParentComponent+ChildDependency, ChildComponent 순서대로 적용
    • ChildInteractor에 private let myStream 추가, init 적용
  • emit & subscribe
    • emit은 ParentInteractor에서 실행
    • subscribe ChildInteractor에서 수행
Comments