brunch

You can make anything
by writing

C.S.Lewis

by Tilltue Jun 30. 2017

iOS 아키텍쳐 바라보기

MVC, MVVM, FLUX, ReSwift, ReactorKit

이 글은 각각의 아키텍쳐를 설명하는 글은 아니다.

아키텍쳐를 알아보고 관련되어 파생된 framework 사용을 고민하는 과정에서 정리한 내용을 기록하기 위해 작성한 글이다. 지극히 개인적인 의견들이 포함되어있다.

글 내용을 제외하고 아래 링크된 여러 글들만 모아 봐도 도움이 될것이라 생각된다.



1. 좋은 앱을 만들기 위해서는 어떤 아키텍쳐가 필요할까?


(서비스 관점이 아닌 앱 개발관점에서) 좋은 앱이라는 건 완성도가 높은 앱이라고 생각한다.
버그가 없고, 반응 속도는 빠르며, 유저가 원하는 동작에 빠르게 도달한다면 좋은 앱이라고 생각한다.


좋은 아키텍쳐를 통해 버그를 줄일수 있고, 테스트를 통해 앱의 안정성을 높이고, 이에 따라 개발 생산성을 높일수 있다면 앱의 퍼포먼스 향상이나, 편의 기능개발에 더 많은 노력을 기울일수 있을것이라는 기대를 가질수 있을것 같다. 하지만, 아키텍쳐가 어려워 생산성이 오히려 저하된다면, 반대의 효과를 가져올 것 같다.


위와 같은 이유로 좋은 앱을 만들기 위한 아키텍쳐는 도입하기 쉽고, 테스트를 통해 안정성, 생산성 향상을 기대할 수 있어야 한다고 생각한다.


혼자서 진행하는 프로젝트에서 복잡한 아키텍쳐를 사용해야 하는지에 대해서는 스스로의 판단에 따르면 될겠지만, 팀으로 진행되는 프로젝트의 경우, 각각의 팀원들이 아키텍쳐의 필요성에 대해 공감대를 형성할수 있어야 된다고 생각한다. 아키텍쳐를 사용하므로써 얻을수 있는 가치에 대해 공감해야 한다.


2. 좋은 아키텍쳐란 무엇일까?


iOS 아키텍쳐 패턴 MVC, MVVM, VIPER, MVP 패턴에 대한 이야기. 링크 모음
https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52
http://news.realm.io/kr/news/krzysztof-zablocki-mDevCamp-ios-architecture-mvvm-mvc-viper/

MVVM 패턴에 대한 이야기
https://justhackem.wordpress.com/2017/03/05/mvvm-architectural-pattern/


첫번째 링크들의 내용중에 Apple 의 기본아키텍쳐인 MVC 를 따라 구성했다고 생각했지만 문제가 있음을 느끼게 될때 당신의 코드는 아래와 같을 것이라고 이야기 한다.


- View Controller 의 코드가 비대하다.
- View 는 아무일도 하지 않는다.
- Model 은 그저 단순한 Data 구조일 뿐이다.
- 뷰 컨트롤러는 뷰의 라이프 사이클과 강하게 역여있어, 재사용이 불가능하다.
- 뷰와 컨트롤러의 상호작용 (유저 action과 그에 따른 로직) 은 unit test 에서 테스트 할수 없다.


위 링크들에서 언급된 좋은 아키텍쳐에 대한 특정을 정리
- 각각의 객체들은 구체적이고 명확한 역할을 가져야 한다.
- 테스트 가능성.
- 사용의 용이성, 낮은 유지 보수 비용.
- 단순한 데이터 흐름. 쉬운 디버깅.
- 관심사의 분리.


가장 인기 있는 패턴인 MVVM을 구현할때 잘못이해하고 있는 부분에 대해서는 꼭 주의깊게 읽어 보길 추천한다.

+ MVVM 패턴을 사용하면서 라우터의 부재로 발생하는 문제점에 대해서도.




ReSwift Readme ( Redux 개념 )
http://reswift.github.io/ReSwift/master/getting-started-guide.html

ReactorKit Readme ( Flux + reactivex )
https://github.com/ReactorKit/ReactorKit

참고 : FLUX 아키텍쳐 Overview
https://haruair.github.io/flux/docs/overview.html

MVVM 패턴을 사용한 kickstarter 의 iOS 오픈소스
https://github.com/kickstarter/ios-oss/tree/master/Kickstarter-iOS


아키텍쳐 오픈소스 및 도움이 되는 샘플 오픈소스이다.

아래에 소스 분석을 통해 살펴본 내용을 정리했다.


MVVM


MVVM 패턴을 도입한 kickstater의 코드를 살펴보자. ViewModel부분을 중점으로.
https://github.com/kickstarter/ios-oss/tree/master/Kickstarter-iOS/ViewModels
https://github.com/kickstarter/ios-oss/tree/master/Library/ViewModels

특징
- inputs, outputs 으로 역활을 분리했다.
- 비지니스 로직이 viewmodel 에서 수행된다.

( 2018.10.1 수정 : 잘못 분석했다. 모델에서 수행된다. )
- viewController 의 단순 이동은 viewModel 의 output 을 통해서 수행된다.
- 앱 상태 제어, 뷰 흐름 제어를 어느 한부분이 담당해서 하지는 않는 것으로 보인다.


장점: 예제가 풍부하고, 이 구조를 따라서 작성하기만 해도 좋은 결과를 얻을 수 있다고 생각한다.

먼저 언급된 MVVM에서 라우터의 부재에 대한 고민은 해봐야 할 것이다.

[최근(2018)에 작성한 MVVM 학습정리 글]


ReSwift

요약

Redux 아키텍쳐의 swift 버전이라고 이해했다. 앱의 상태는 State 로 정의되며, 강력한 단방향 흐름을 가진다. Action 을 통해 State는 변경될수 있고 이것은 reducer 가 담당한다.
Store 에 하나의 State 가 담겨지고 이 history 는 기록이 가능해 재현이 쉽다.
View 들은 관심있는 state를 subscribe 하며 액션을 전달하거나 state를 뷰에 반영한다.


문제점

ReSwift 에서 상태변화에 따라 view controller flow 관리를 돕는 ReSwiftRouter framework 의 경우 내부 동작을 살펴보면 새로운 state 전달 -> 마지막 navigation state route 에서 새로운 state route 로 전환을 액션을 만드는데  이전 상태와 새로운 상태에 대한 routable 프로토콜은 준수하는 상태 변화들에 대해 처리하는 route 액션들을 생성한다.

내부 Serial Queue 에 이 route 액션들을 넣어 순차적으로 처리하며 pop , push , change (routable 프로토콜) route action type 에 따라 메인 queue 에서 viewcontroller change 를 수행하도록 하며 이 수행 과정동안, semaphore 로 completion에서 시그널을 보낼때까지 3초간 대기한다. 시간초과시 timeout 이 발생. 이후 처리는 아직 미구현 상태.

만약 route 액션이 중첩되어 발생될 경우에 문제가 발생할만한 구조이다. cancel 이 없기 때문인데, 이미 큐에 등록된 action은 순차적으로 pop, push, change 중 하나를 수행해야만 한다.


이런 구조에서 아래와 같은 route 액션이 짧은시간안에 발생하는 것을 가정하면,


네트워크 단절 감지 -> 네트워크 alert show Action 발생. -> Alert Show State 상태 변경 -> Router 에 State 전달 -> alert viewcontroller present

push notification 수신 ->  특정 ViewController show action 발생. -> ViewController show State  상태 변경 -> Router 에 state 전달. -> viewController present


ReSwiftRouter 의 구현내용을 보면 네트워크 alert 이 뜬뒤에 push 노티 상태 변경을감지해서 pop protocol 을 수행해 alert 은 dismiss 되고 이후에 2번째 액션에 따른 viewcontroller 가 present 될걸 예상할 수 있다.

이렇게 되면 유저가 네트워크 단절 알림을 정확히 인지하기도 힘들며, 불필요한 present , dismiss 과정이 수행될 수 있다.

만약, 10번의 route 액션이 짧은 시간에 발생된다고 보면 문제는 더 심각해진다.

이를 해소하기위해서는 Router 에 상태 전달을 하기 전에 Router state 변경의 우선순위를 관리하는 중간 객체가 필요할것으로 보인다. 이럴 경우 ReSwiftRouter를 쓰는 장점을 찾기 힘들어지는 것이 아닐까?


아직 ReSwiftRouter를 사용하기에는 무리가 있어 보인다. 아래는 이슈 목록이다.

https://github.com/ReSwift/ReSwift-Router/issues


ReSwiftRouter 를 사용하지 못한다면, ReSwift 에서 Store 에 담겨진 상태와  ViewController flow 와 연결시켜 줄 수 있는 관리 객체를 만들어 사용해야 한다. Redux 아키텍쳐 도입을 위해 ReSwift 를 선택하려 했다면 이 부분을 고민해야 할 것 같다.


- ReSwift  을 사용할때 예상되는 어려움

샘플은 비교적 단순한 기능을 가진 소스들이기 때문에 복잡도가 높아질때 어떻게 처리해야 할지에 대해서는 스스로 설계해 나가야 한다.
Redux,Flux 아키텍쳐는 매력적이지만 유저가 view 를 통해 시작되는 action이외에 디바이스 상태, UIKit의 delegate등에서 발생되는 action 등이 섞여 질때에도 State관리는 용이할까? Reducer의 역활이 잘 분리되도록 설계하는데에 어려움은 없을까?


ReactorKit


Flux + reactivex (rxswift )
한국인(전수열님)이 만든 프레임워크이기때문에 문의에 대한 피드백이 빠르고 정확하게 이어질 것을 기대할 수 있다. Action,State,View,Reactor 를 가지며 redux와도 비슷하나, redux 와 차이점은 State 를 전역적으로 하나만 두지는 않았다는 점이다. 이 때문에 특정 부분에만 ReactorKit 을 적용할 수 있다.

어렵지 않은 framework 라고 생각된다.


viewcontroller (혹은 view) 에서 ReactorKit을 사용하는 예를 들어 설명하면,

Reactor를 하나 가지고 있고,  Reactor에 상태들이 정의되며, 그 상태 변화를 수행하는 mutate Observable 을 가지고, 이를 이용해 reduce에서 새로운 상태로 변경된다.

주) Reactor 는 view 에 종속적이지 않아도 된다. 뷰가 없는 Reactor 를 만드는것도 가능하다.


Reactor 에 action 이 전달되면 mutate 를 통해 상태 변경을 위한 일을 수행하고 reduce 로 상태가 변경된다. viewcontroller 는 Reactor 에서 상태가 변경되면 각 상태와 binding된 일들(UI갱신등을) 수행한다.

Reactorkit 은 작은 규모의 아키텍쳐이다. viewcontroller 의 흐름제어에 관여하지 않는다.

이 부분이 Flux, Redux, ReSwift 와 가장 큰 차이점이라고 생각한다.


viewcontroller(혹은  view) 에서 Reactor의 action, state 에 바인딩 하는 예

- Acction binding 의 예: 버튼탭, viewdidload 등 조건이 주어질때 액션을 발생.

- State binding 의 예: 로딩 상태에 따른 UI 노출 등. action 완료 상태에 대한 UI갱신등.


ReactorKit 을 사용할때 고민되는 부분.


- 데이터의 흐름은 하나의 viewcontroller 에 제한적이다.

- viewcontroller 간의 의존성이 발생할 경우에는 어떻게 처리해야 하는가?

Flux 의 개념이 하나의 viewcontroller 에서만 제한적으로 이루어 지기 때문에 Flux 아키텍쳐의 장점을 모두 얻는다고 보기는 힘들것 같다.

- Reactor 가 추가되어 조금 쪼개진 viewcontroller 인 것은 아닐까?


예를 들어, 프로필을 보여주는 profile viewcontroller 가 있고, 프로필을 수정하는 profile edit viewcontroller 가 있다.

프로필을 수정하는 profile edit viewcontroller 의 경우 프로필 변경됨 action 이 발생한다.


ReactorKit 의 view communication 가이드라인에 따르면

A ViewController ( edit ) 는 event 를 B ViewController 에 전달하고 B ViewController 는 이것을 action 으로 B ViewController 가 가진 reactor에 전달된다.


본래의 Flux 아키텍쳐라면, profile edit action 이 store 에 등록된 callback을 통해 모든 store 에 전파되고, store가 action 을 처리했다면 이 액션이 처리되었음을 알리며, 이 프로필 변경 이벤트에 관심이 있는 뷰는 이벤트를 받고 새로운 데이터를 받아와(혹은 전파 받아) 뷰를 갱신할 것이다.

profile edit viewcontroller 는 특정 view controller 에 이벤트를 전달하지 않아도 되며, 액션만 발생할 것이다.


ReactorKit 에서는 이벤트를 전달해야 한다. Global States 를 통해 transform 에서 이벤트를 관찰하는것도 방법으로 제시하지만, 이럴때마다 Global States를 만드는 것은 좋지 않은 것 같아 고민이 되는 부분이다.

Reactor 는 view에 종속적이지 않게도 만들수 있으므로, Global 상태 관리용 Reactor 를 추가해서 이 고민을 해결할 수도 있을것 같다. 이부분에 대해서는 설계 예가 없으므로 flux, redux, reswift 의 글로벌 상태 처리부분을 좀더 공부해야 할것 같다. Global 상태 관리용이 아니더라도, 이벤트 전달용 Reactor 만을 설계해도 이 고민을 해소 할 수 있을 것 같다.

데이터 흐름이나, viewcontroller 간의 의존성문제도 이런 중간 계층의 reactor 를 통해 해소할수 있을까?


ReactorKit은 위에 언급한 좋은 아키텍쳐에 체크리스트의 대부분을 만족한다고 생각한다.

또, 테스트 코드에 대한 샘플도 풍부하니 적용하는데 있어서 어려움은 없을것으로 보인다.


ReactorKit 을 사용한 오픈소스 프로젝트 제작기



어떤 아키텍쳐를 가지고 프로그래밍을 하든, 그 아키텍쳐를 통해 얻고자 하는 것들에 대해서 가장 중요하게 생각해야 한다. 아키텍쳐를 공부하고 고민하고, 실제로 적용을 해보는 노력을 하다보면, 결과적으로 점점더 좋은 코드를 만들어 내고 있지 않을까?

매거진의 이전글 iOS 한글이름 문서 미리보기 안될시
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari