티스토리 뷰
왜 아키텍쳐를 고려해야만 할까?
아키텍쳐를 고려하지 않는 다면 매우 비대해진 클래스를 마주하게 됩니다. 그리고 이를 디버깅하는 동료 뿐만 아니라 본인 조차 이해하고 수정하는 데 너무나도 많이 시간을 허비해야만 합니다. 결국 수정을 포기하게 되죠(?)
서비스 규모가 커짐에 따라 코드 또한 빠른 속도로 방대해 집니다. 그래서 우리는 이러한 상황에 대비하기 위하여 좋은 설계가 필요합니다.
좋은 아키텍처란 ?
- 각 객체들이 구체적이고 명확한 역할을 가지며, 그 역할이 적절하게 분배되어 있다.
- 데이터의 흐름이 단순하다.
- Testability 하다.
- 코드의 위치가 명확하다.
이 기준들을 가지고 하나씩 장단점을 따져봅시당
MVC
Pros
- 개발자에게 너무나도 익숙한 구조.
Cons
- Controller가 View Lifecycle과 강하게 연결되어있어 View와 Controller 간의 분리가 힘들다
- 역할의 분리가 되어있지 않아 Testability x
- Controller(View)에 모든 로직이 들어가 매우 비대해진다.
이러한 Cocoa Framework의 특성탓에 아래부터는 View와 Controller를 View로 묶어서 정의합니다.
MVP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | let model = Person(firstName: "David", lastName: "Blaine") /* Model 모델은 모델 */ let view = GreetingViewController() /* View - UI로 시작하는 모든 클래스들을 뿌리는 역할. - view 는 model 에 대해서 알지 못함. */ let presenter = GreetingPresenter(view: view, person: model) /* Presenter - View를 뿌리는 layout code에는 관심 x. - 단지 View의 state, data를 update 한다. - server 나 db 에서 받아온 데이터를 직접 관리한다. */ view.presenter = presenter // View와 Presenter 가 1:1로 대응된다. | cs |
Pros
- View를 정적인 상태로 만든다 (할일 줄여주자)
- Presenter는 ViewLifecycle과 전혀 상관없다 (오!)
- Presenter는 Layout에 관련된 코드가 일체 없다. (관심사 분리 오!)
- Presenter는 단지 View의 state와 data 를 업데이트한다.
- Testability : Present를 보면 unit test case 들을 알 수 있다.
Cons
- View와 Presenter가 1:1로 대응된다.
- Presenter 가 단순 중개자이기 때문에 여전히 View의 역할이 크다. (또는 불분명해진다.)
- Presenter의 코드가 빠르게 방대해진다...
MVVM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | let model = Person(firstName: "David", lastName: "Blaine") /* Model 모델은 모델 */ let viewModel = GreetingViewModel(person: model) /* ViewModel - viewModel은 view에 대해서 알 필요가 없다..!! - 로직의 흐름을 가지고 상태를 update 한다. */ let view = GreetingViewController() view.viewModel = viewModel /* View - model 에 대해서 알필요 x. - server 나 db 에서 받아온 데이터를 ViewModel에 넘겨준다. - ViewModel 의 상태변화를 감지해서 스스로 update 한다. */ | cs |
Pros
- MVP 의 Presenter가 가지는 장점을 그대로 갖는다.
- View → ViewModel 바인딩 → View와 ViewModel 간의 의존성 제거 (단방향 이벤트 수신)
- Rx를 적극 활용할 수 있다.
- 데이터의 정제(상태변화)를 직접 관리하기 때문에 View의 역할을 줄여준다.
Cons
- MVP에 비해 View의 역할이 많다. (ViewModel은 View를 직접 update 시키지 않는다)
- 아키텍쳐의 이해가 저마다 달라 ViewModel 역할 범위에 대한 논쟁이 많다...
Viper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | let view = GreetingViewController() /* View 사용자 입력을 Presenter에 보내고 Layout 작업을 수행. */ let interactor = GreetingInteractor() /* Interactor Use case 에 따라서 모델 개체를 조작. */ let presenter = GreetingPresenter() /* Presenter Interactor로부터 데이터를 받아와 View에 언제 보여줄지 결정. */ view.eventHandler = presenter presenter.view = view presenter.greetingProvider = interactor interactor.output = presenter /* Entity 모델은 모델 */ /* Router - 화면 전환을 담당 - 화면전환을 어떻게 할 것인지 결정. */ | cs |
Pros
- MV(X) 들과 달리 화면 전환에 대한 역할 또한 분리
- 역할 분리에 있어서 최강자
- Testability 또한 최강
Cons
- 이걸 지킬 수 있는 곳이 있을까...
ReactorKit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | let view = GreetingViewController() /* View - no business logic, only layout - action stream과 state stream을 바인딩 하는 역할만 수행한다! */ let reactor = GreetingReactor() view.reactor = reactor /* Reactor - view의 state 만 관리. - business logic 담당. 3 Streams - Action : user의 action. - Mutation : state 변화 - State : 현재 view 의 state 3 Funcs - mutate() : Action을 받아 Mutation 생성 - reduce() : 이전의 State 와 Mutation을 받아 새로운 State 생성 - transform() : 각 stream을 전송한다. Observable for (Action, Mutation, State) */ | cs |
Pros
- 한국의 갇수열(전수열)님께서 만들어 주셨음 !
- Unidirectional : View는 Action만, Reactor는 State만 보낸다 !
- 부분적으로 차용하는 것이 가능한 작은 규모의 아키텍쳐
- 위의 것(?) 들과 마찬가지로 View가 정적이라 Testability 확보.
Cons
- -
RIBs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | let interactor = GreetingInteractor() /* Interactor - Business Logic 담당. - state 변화, data 저장장소, RIBs 이동 결정. */ let router = GreetingRouter() /* Router - Interactor의 변화를 감지하고 child RIBs를 연결할지 뗄지 결정 - HumbleObjects : Interactor의 Testability 확보 - RIBs 간의 직접적인 소통이 아니라 Router를 거쳐 Interactor 간의 소통 - Routing 알고리즘을 통해 Interactor의 역할을 부담함으로써 core 비즈니스 로직에 집중. */ let builder = GreetingBuilder() /* Builder - RIBs의 모든 구성 class 작성 로직을 담당 - 로직을 분리함으로써 Mockability 확장 및 DI 독점 */ let dependency = GreetingDependency() /* Dependency - 특정 RIB가 Parent RIB로부터 인스턴스화해서 받아야할 종속성(변수,함수)을 나열하는 프로토콜. */ let component = GreetingComponent() /* Component - RIB Dependency를 관리. (Dependency 프로토콜의 구현) - Builder를 보조하여 RIB 구성요소를 생성. - Parent RIB의 Component는 Parent RIB의 Dependency 사용을 위해 Child RIB의 Builder에 주입된다. */ // MARK: Optional let presenter = GreetingPresenter() /* Presenter - 정적인 Stateless class 이다. - Business model 을 View model로 변환할때 사용 - 하지만 이 변환이 너무 사소할 때가 많아 optional 이다. */ let view = GreetingViewController() /* View(Controller) - UI layout 작업 - 가능한한 "dumb"하게 작성한다. */ component.dependency = dependency builder.component = component presenter.view = view interactor.presenter = preseneter builder.interactor = interactor router.routing = builder.build() | cs |
Pros
- 템플릿화가 잘되어 있다.
- 프로토콜 지향을 할 수 밖에 없는 구조 !
- **View 단위가 아닌 프로세스 단위로 흘러간다
- 복잡한 로직 흐름을 모듈화 및 재사용하는 데에 특화되어있다.
- DI 로직이 깔끔하게 분리되었다.
- VIPER의 상위호환 이라고 한다(?) (우버에서 쓴대잖아..)
Cons
- 적용 사례가 많지 않아 도입 장벽이 존재한다.
- 프로토콜 지향은 좋지만 강제성이 불편한 경우가 많다.
- 파일이 정-말 많이 생성된다.
Conclusion
이렇게.. iOS 에서 주로 사용되는 아키텍쳐들에 대해서 알아 보았습니다.
어떤게 좋다 나쁘다 하는 것은 없지만 시간의 순서대로 작성하면서
- View는 정적인 상태로 만들어 Testability 를 확보하자.
- 프로젝트 확장에 대비하여 로직을 분리하자.
- 명확한 코드 위치 및 데이터 흐름을 유지하자.
위의 세 가지가 주요 관심사라고 생각합니다.
이 포스팅에서 간략한 요약과 장단점을 비교하였는데, 적용하실 프로젝트 스펙에 맞춰 튜토리얼을 진행해보시는 것이 가장 좋다고 생각합니다 ! (무책임..)
그리고 Rx 등과의 시너지 효과 등 다양한 사항을 고려해야하기 때문에 뭐가 좋나요? 라는 것은 가장 어려운 질문인 것 같습니다 ㅠ
저도 공부하면서 작성하였기에 부족한 부분이 많네요 ㅠㅠ 댓글로 피드백 주시면 너무 감사드리겠습니다 !
그럼 20000 !
참고 : https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52
'iOS' 카테고리의 다른 글
[iOS] NotificationCenter 톺아보기 (0) | 2019.09.05 |
---|---|
[iOS] Grand Central Dispatch (GCD) 톺아보기 (0) | 2019.09.05 |
[iOS] App Transport Security (ATS) 톺아보기 (0) | 2019.09.05 |
[iOS] URLSession, URLSessionDataTask 톺아보기 (0) | 2019.09.05 |
[iOS] Tab Bar 톺아보기 (0) | 2019.09.04 |