반응형
이번에는 RIBs/wiki 보면서 RIBs에 대해 배워보겠습니다.
RIBs 개념 정리
- RIBs는 크로스 플랫폼 아키텍처 프레임워크
- 프레임워크는 정해진 틀에 코드를 넣으면 시스템이 약속된 기능을 작동시켜주는건데, 이 개념에 맞는지는 좀 더 살펴봐야겠습니다.
- RIB을 작성하는 템플릿이 있지만 프로그래머가 직접 관계를 지정해줘야하는 면에서 라이브러리라고 볼 수 있는건 아닐까? 하는 생각은 들었습니다.
우버를 위해 이 프레임워크를 디자인 했을때, 다음 원칙을 고수함:
- 크로스 플랫폼 협력을 독려함
- iOS와 Android 앱에서 대부분의 복잡한 부분은 비슷함
- RIB는 iOS와 Android에 비슷한 개발 패턴을 제공함
- 의문점
- 아키텍처가 통일된다고 해서 서로의 코드를 공유하는 일이 있을까? 하는 의문이 들기는 합니다.
- 아키텍처가 달라서 문제가 되는 부분이 있을지 의문도 들었습니다.
- 모바일 팀 매니저 입장에서는 관리하기 편하겠다는 생각도 듭니다.
- 여러 플랫폼에서 동일한 아키텍처를 적용하는 시도를 아직 못해봤는데, 일단 한번 경험해보고 싶다는 생각이 들었습니다.
- 전역 상태와 결정을 최소화
- 전역 상태를 변경하시는 것은 예측하지 못한 행동을 불러일으킬 수 있음
- 계층화된 RIB들에 상태값을 캡슐화 하도록 함
- 테스트 및 격리
- 클래스는 유닛 테스트 하기 쉬워야 함
- 클랫스는 독립적으로 추론 가능해야 함
- 독립된 RIB는 고유한 책임을 가짐
- 부모 RIB 로직은 하위 RIB 로직으로 부터 대부분 분리됨
- 대부분? 예외로 안되는 경우도 있나보다
- 개발자 생산성을 위한 도구 제공
- 코드 생성
- 정적 분석
- 런타임 통합
- 이건 뭐지?
- 개방 폐쇄 원칙
- 개발자가 기존 코드 수정 없이 새로운 기능을 추가할 수 있어야 한다
- 부모 RIB에 의존성이 필요한 child RIB을 코드 변경 없이
attach
또는build
할 수 있다
- 비즈니스 로직을 중심으로 구조화
- 비즈니스 로직이 UI 구조를 반영할 필요는 없음
- 애니메이션 & View 퍼포먼스를 높이기 위해서 View 구조는 RIB 구조보다 얕은 계층 구조로 만들 수 있음
- 또는 하나의 RIB이 다른 여러 UI를 컨트롤 할 수 도 있음
- 의문점
- View에 비즈니스 로직을 가두는 구조가 아닌건 흥미로움
- 그러나 이런 케이스가 얼마나 있을까 하는 의구심도 생김
- 구체적인 예를 볼 수 있으면 좋겠습니다.
- 명시적 계약(Contracts)
- 요구사항들은 컴파일 타임 계약으로 명시되어야 함
- 클래스와 순서에 대한 의존성이 만족되지 않으면 클래스는 컴파일 되지 않아야 함
- 순서에 대한 의존성은 무엇일까? Ordering dependencies
- 순서 의존성(ordering dependency)을 표현하기 위해
ReactiveX
를 사용함- 이 부분 무슨말인지 하나도 모르겠음... 나중에 다시 읽어볼 것We use ReactiveX to represent ordering dependencies, type safe dependency injection (DI) systems to represent class dependencies and many DI scopes to encourage the creation of data invariants.
RIB 구성 요소
VIPER 아키텍처를 사용하고 있었다면 RIB의 클래스들이 익숙할 것임
Interactor
- 비즈니스 로직이 담겨짐
- Rx subscription을 수행하는 곳
- 이건 뭔지 잘 모르겠음
- 상태 변경을 결정함
- 어떤 데이터를 어디에 저장할지 결정함
- 어떤 child RIB이 attach 되어야할지 결정함
Router
Router는 Interactor의 Output을 듣고 자식 RIB을 attach
또는 detach
하는 것으로 변환 시켜줌
아래 3가지 이유로 존재함
1. Router는 Humble Object로 기능한다. Child interactor를 mock 하거나 신경쓰지 않고서, 복잡한 Interactor 로직을 테스트하기 편하게 하기 위한 역할.
Humble Object는 무엇일까?
더보기
https://martinfowler.com/bliki/HumbleObject.html
테스트하기 어려운 객체를 만남 -> 로직을 테스트 가능한 코드로 이동시킴, 원래 객체는 humble 해짐
2. Router는 부모와 자식 Interactor간에 추가적인 추상 레이어를 만듦.
- 인터렉터 간 synchronous communication을 어렵게 만든다
- 직접 결합 대신 reactive communication을 채택하도록 장려함
- 무슨말인지 모르겠다... 실습 해봐야 체감할 수 있을 듯
3. Router는 단순하고 반복적인 라우팅 로직을 포함함
- 그렇지 않았으면 Interactor에 구현되어야 하는 부분
- boilerplate code를 facotring out 하면 Interactor를 작게 유지하고 핵심 비즈니스 로직에 집중할 수 있음
Builder
Builder은 RIB의 구성 클래스들을 생성하는 역할. 하위 RIB들의 builder도 생성해줌.
- 클래스 생성 로직을 분리하는 것
- iOS에서의 mockability를 향상 시킴
- 나머지 RIB 코드를 DI 구현의 세부사항과 무관하게 만듦
- DI 시스템을 인식해야하는 RIB의 유일한 부분
- 다른 DI 매커니즘을 통해서 기존의 RIB 코드를 재사용 가능하다
Presenter
- Presenter는 stateless 클래스.
- Business model을 View model로 변형시켜줌 (반대로도 가능)
- 반대로도 가능한게 좀 신선한데... 그런일이 있을까? 나중에 확인해봐야겠다
- 뷰모델 변환 테스트를 가능하게 함
- 하지만 이러한 변환은 너무 사소한 것이라서 Presenter 클래스가 필요없는 경우가 많음
- Presenter가 생략되면 view model로 변환하는 작업은 View(Controller) 또는 Interactor의 책임이 됩니다
View(Controller)
- View들은 UI를 빌드하고 업데이트 함
- UI 컴포넌트들을 생성하고 레이아웃, 유저 인터렉션 핸들링, 데이터로 UI 컴포넌트 채우기, 애니메이션을 포함함
- 가능한 기능이 없어야 함 (Views are designed to be as “dumb” as possible.)
- 정보를 보여주기만 해야 함
- 일반적으로, 유닛 테스트가 필요한 코드는 여기에 포함되지 않습니다
Component
- RIB dependencies를 관리하기 위해 사용됨
- RIB을 구성하는 다른 unit들을 생성해서 Builder들을 도와줌
- Components는 외부 의존성들에 접근할 수 있도록 해줌
- RIB 자체에서 생성된 종속성을 소유함
- 다른 RIB으로 부터 해당 종속성에 대한 접근을 제어함
- 보통 부모 RIB의 Component는 child RIB의 Builder로 주입된다. 자식 RIB에게 부모 RIB의 종속성에 접근할 수 있도록 하기 위해서
State Management
- 어플리케이션 상태는 현재 RIB 트리에 attach된 RIB으로 표시됨
- 예를들어, 차량 공유 앱에서 여러 생태를 거치면서, 앱은 다음과 같이 RIBs를 attach하고 detach 한다
- RIBs는 그들의 scope 안에서만 상태를 결정한다.
- 예를들어,
LoggedIn
RIB은Request
와OnTrip
같은 전환에만 상태 결정을 한다.
일부 State는 RIB 추가/제거를 통해 보관하지 않음
- 예를들어, 유저 프로필의 세팅을 변경할 때, 어떤 RIB도 attach 되거나 detach 되지 않음
- 일반적으로 불변 모델의 스트림 내부에 이 상태를 저장함. 정보가 변경될 때 값을 재방출함(re-emit).
- 예를들어, 사용자 이름은
LoggedIn
scope 안의ProfileDataStream
에 저장될 수 있음 - 네트워크 Response만 이 스트림에 대한 write 권한을 가짐
- 이러한 스트림들에 대한 read 접근에 인터페이스를 DI 그래프로 전달함
RIB state를 위해 single source of truth를 강제하지 않음
- React 같은 프레임워크와는 대조적
- 각 RIB의 컨텍스트 내에서 단반향 데이터 플로우 패턴을 선택하거나
- 효율적인 애니메이션을 위해 비즈니스 상태와 view 상태가 일시적으로 다른것을 허용해줄 수 있음
Communication Between RIBs
- Interactor가 비즈니스 로직을 결정할 때, 다른 RIB에 알려줘야 할 때도 있다.(event, completion, send data...)
- RIB 프레임워크는 RIB 간에 데이터를 전달하는 single way를 포함하지 않음
- 그럼에도 불구하고, 몇 가지 일반적인 패턴을 가능하게 만들어짐
- 만약 커뮤니케이션이 parent RIB의 Interactor로 올라가면, 커뮤니케이션은 listener 인터페이스를 통해 이루어진다.
- parent가 child보다 오래 존재할 수 있기 때문
- 이것도 이해 못했음
- parent RIB 또는 그것의 DI 그래프 안의 object는 listener 인터페이스를 구현한다.
- 그리고 listener 인터페이스를 그것의 DI 그래프에 위치 시킨다.
- children RIB이 호출할 수 있도록!
- parent가 children의 Rx stream을 직접 subscribe 하지 않고, 데이터를 위로 전달하는이 이 패턴을 통해 얻는 이점
- memroy leak 방지
- 어떤 children이 attach 되었는지 모르고 작성, 테스트, 유지할 수 있음
- child RIB을 attach/detach 할 때 생각해야하는 양을 줄여줌
- child RIB을 이러한 방식으로 attach 하면, Rx stream을 unregistered/re-registered 할 필요가 없음
다음 공부할 거리
- 튜토리얼 따라하기: https://github.com/uber/RIBs/wiki/iOS-Tutorial-1
- 의문이 생긴 부분들 튜토리얼 후에 다시 생각해보기
반응형
'iOS > Architecture' 카테고리의 다른 글
RIBs 스터디 1: Let'Swift 발표들로 RIBs 맛보기 (0) | 2021.04.20 |
---|