Swift RIBs
안녕하세요. 천원입니다.
이번에 KWDC2023을 방문 했었는데 모듈화 세션에서 카카오뱅크에서는 RIBs 아키텍쳐 패턴을 사용해서 개발을 한다는 이야기를 듣고 한번 공부해보고자 공식문서를 정리 해보겠습니다.
RIBs ?
RIBs는 Uuber의 크로스 플랫폼 아키텍처 프레임워크로 이 프레임워크는 많은 nested states(중첩된 상태)를 포함하는 대규모 애플리케이션용으로 설계되었습니다.
아키텍처인데 프레임워크라니 신기하네요.
RIBs의 설계 원칙
- Encourage Cross-Platform Collabroation: iOS와 Android 단일 아키텍처의 사용이 가능
- Minimize Global States and Decisions: 잘 격리된(캡슐화) RIB을 통하여 코드 수정시에 전역상태의 변경을 방지합니다.
- Testablilty and Isolation: 개별 RIB은 고유한 책임을 가지고있어서 쉽게 테스트하고 독립적으로 추론이 가능합니다.
- Tooling for Developer Productivity: RIBs는 코드 생성, 정적 분석 및 런타임 통합과 관련된 IDE 도구와 함께 제공되어 개발자의 생산성을 향상 시킵니다.
- Open-Closed Principle: 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있어야합니다.
- Structured around Business Logic: 앱의 비지니스 로직 구조는 UI를 엄격하게 미러링할 필요가 없습니다. 하나의 RIB이 서로 다른 위치의 여러개의 View를 제어할 수 있습니다.
- Explicit Contracts: 클래스 종속성과 순서 종속성이 충족되지 않으면 클래스를 컴파일하면 안됩니다. ReactiveX를 사용하여 종속성 순서를 지정하고 안전한 종속성 주입 시스템을 사용하여 클래스 종속성을 나타냅니다.
RIBs 구성요소
Interactor
- 비지니스 로직
- Rx 구독
- 다른 RIB을 자식으로 연결할지 결정
- 상태 변경 결정을 내림
- 데이터를 저장할 위치 결정
Interactor가 수행하는 모든 작업은 반드시 lifecycle에 제한되어야 합니다. Interactor가 활성화된 경우에만 비즈니스 로직이 실행되도록 구현되어 있다고 합니다. 이렇게 하면 비활성화 되어있을 때 비즈니스 로직 또는 UI의 원치 않은 업데이트를 방지할 수 있습니다.
Router
라우터는 Interactor를 수신하고 그 출력을 하위 RIB에 Input, output으로 변환합니다.
- 하위 Interactor를 mock 하거나 복잡한 Interactor로직을 쉽게 테스트 할 수 있습니다.
- 상위 Interactor와 하위 Interactor간에 추가 추상화 계층을 만듭니다. 이로 인해 Interactor간의 통신이 조금 더 어려워지고, RIBs간의 직접 연결 대신 반응형 통신이 채택에 권장됩니다.
- Interactor가 구현하는 단순하고 반복적인 라우팅 논리가 포함되어 있습니다. 이 코드를 제외하면 Interactor를 작게 유지하고 RIB에서 제공하는 핵심 비즈니스 로직에 집중이 가능합니다.
Builder
빌더의 책임은 모든 RIBs의 구성 클래스와 각 RIB의 자식에 대한 빌더를 인스턴스화하는 것입니다. (모든 RIB을 하나로 만들어 주는 구조체)
Presenter
비즈니스 모델을 ViewModel 또는 그 반대로 변환하는 상태 비저장 클래스입니다. Presenter의 역할은 사소해서 생략이 가능하며 생댝되면 ViewModel의 번역은 View 또는 Interactor의 책임이 됩니다.
View(Controller)
View는 UI를 빌드하고 업데이트합니다. View는 가능한 dumb하게 설계되었습니다. 단지 정보를 표실 할 뿐이며 일반적으로 Unit Test가 필요한 코드는 포함되어 있지 않습니다.
Component
Component는 RIB의 종속성을 관리하는데 사용합니다. RIB을 구성하는 다른 유닛을 인스턴스화하여 빌더를 돕습니다.
State Management
현재 RIB 트리에 연결된 RIB에 의해 관리되고 표시됩니다. 예를 들어 사용자가 승차 공유 앱에서 여러 상태를 진행함에 따라 앱은 다음 RIB을 연결 및 분리합니다.
RIB은 해당 범위 내에서만 상태 결정을 내립니다. 예를 들어 LoggedIn같은 상태 간 전환에 대한 결정만 내리고 우리가 스크린에서 어떻게 행동할지 결정하지 않습니다.
RIB을 추가하거나 제거하여 모든 상태를 저장할 수 있는 것은 아닙니다. 예를 들어 로그인한 사용자가 프로필 설정을 변경하게 되면 RIB이 연결되거나 분리되지 않습니다. 일반적으로 이 상태는 세부사항이 변경될 때 값을 다시 방출하는 불변 모델 스트림(streams of immutable models)에 저장됩니다.
Communication Between RIBs
Interactor가 비즈니스 로직 결정을 내릴 때 완료와 같은 이벤트를 다른 RIB에 알리고 데이터를 전송해야 할 수 있습니다. RIB 프레임워크에는 RIB간에 데이터를 전달하는 단일 방법이 없습니다. 그럼에도 몇 가지 일반적인 패턴을 용이하게 하기 위해 만들어졌습니다.
일반적으로 하위 RIB으로 하향 통신하는 경우 이 정보를 Rx 스트림으로 전달합니다. 또는 데이터가 자식 RIB의 build() 메서드에 대한 매개변수로 포함될 수 있으며, 이 경우 매개변수는 자식의 수명 동안 변하지 않습니다.
RIB트리에서 상위 RIB의 Interactor로 통신이 진행되는 경우 상위가 하위보다 오래 지속될 수 있으므로 통신은 리스터 인터페이스를 통해 수행됩니다. 상위 RIB 또는 DI 그래프의 일부 객체는 리스너 인터페이스를 구현하고 하위 RIB가 호출할 수 있도록 DI 그래프에 배치합니다. 부모가 자녀의 Rx 스트림을 직접 구독하도록 하는 대신 이 패턴을 사용하여 데이터를 위쪽으로 전달하면 몇 가지 이점이 있습니다. 이는 메모리 누수를 방지하고 어떤 자식이 연결되었는지 알지 못한 채 부모를 작성, 테스트 및 유지 관리할 수 있도록 하며 자식 RIB를 연결/분리하는 데 필요한 의식의 양을 줄입니다. 이런 방식으로 자식 RIB를 연결할 때 Rx 스트림이나 리스너를 등록 취소/재등록할 필요가 없습니다.
공식문서를 정리해 봤는데요.. 이해하기가 쉽지가 않네요..
다음은 튜토리얼..