우리는 건축물을 만들고, 그 후에 건축물이 우리를 만드는 것을 아키텍처의 범주로 잘 알려져있다. 결국 모든 프로그래머가 배움으로서 이것은 그냥 소프트웨어 구축을 보다 더 좋게 하는데 적용된다.
우리가 짠 코드들이 간결한 아이덴티티가 부여되고 명확한 목적을 가지며, 각 코드가 논리적인 측면에서 서로 맞아 떨어지게 설계하는 것이 중요하다. 이것이 바로 소프트웨어 아키텍처가 의미하는 바이다. 좋은 아키텍처는 제품의 성공이 아니라 제품의 유지보수 용이성과 사람들이 유지보수를 할 때 제정신을 차리도록 멘탈을 보호해주는 것이다!
이 글에선 VIPER라 불리는 아키텍처를 iOS에 적용시켜 소개해보려고 한다. VIPER는 많은 큰 프로젝트에 사용되어 왔으나, 이 글의 목적상 간단한 to-do 리스트 앱을 통하여 VIPER를 보여줄 예정이다. 여러분은 여기 Github에 예제 프로젝트와 함께 따라오길 바란다.
VIPER란?
iOS 앱을 만들면서 테스트는 주요 작업이 아닐 때가 많다. 우리는 Mutual Mobile에 있을때 테스팅 절차를 들여라는 요사항이 들어왔는데, iOS 앱을 위해 테스트를 준비하는게 쉽지 않다는 것을 깨달았다. 우리는 소프트웨어를 테스트 할 수 있는 방법을 개선하기로 결정하였고, 앱의 아키텍처를 더 좋은 방법으로 만들 필요가 있었다. 그렇게해서 나온 해답이 바로 VIPER라 불리는 것이다.
VIPER는 iOS 앱들에게 클린 아키텍처의 어플리케이션이다. VIPER라는 단어는 VIew, Interactor, Presenter, Entity, Routing의 약자로 구성된다. 클린 아키텍처는 논리적인 구조를 앱의 책임별 각 층으로 나눠준다. 이것은 의존성을 고립시키고 각 층들 사이 경계에서의 상호작용을 테스트하기 쉽게 해준다.
대부분의 iOS 앱은 MVC(Model-View-Controller) 아키텍처를 사용한다. 앱에서 MVC 아키텍처를 사용하는 것은 당신으로 하여금 모든 클래스들이 model이자 view이자 controller라 생각들게 만들것이다. 그러므로 대부분의 앱 로직은 model이나 view에 들어가있지 않고 controller에 몰려있다. view controller가 비대해지면서 결국 MassiveViewControlle 문제가 되버리고 만다. 이 비대한 view controller의 중량을 줄여 코드의 질을 높히는 과제는 iOS 개발자만이 직면한 문제가 아니지만, 이러한 시도는 좋은 시작 시점에 와있다.
VIPER의 각 층은 명확한 위치에 앱 로직이 들어가고, navigation 관련 코드가 되도록 함으로서 이러한 과제를 해결하는데 도움을 준다. VIPER를 적용함으로서, 우리 to-do 리스트 예제에서의 view controller가 머신을 조종하는 view에 의존한다는 것을 당신은 인지하게 될 것이다. 또한 view controller에 있는 코드와 모든 다른 클래스들이 이해하기 쉽고, 테스트하기 쉬우며, 그리하여 유지보수하기까지 쉽다는 것을 깨달을 것이다.
유스케이스에 기반한 앱 설계
앱은 종종 유스케이스의 한 집합으로 구현된다. 유스케이스는 기준이나 동작을 수용하고 앱이 어떤 일을 의도하는지 설명하는 것으로 알려져 있다. 리스트는 날짜, 타입, 이름으로 정렬이 가능해야 한다고 정의하는 것이 유스케이스이다. 한 유스케이스는 앱에서 비즈니스 로직을 위한 기능인 한 층(layer)이다. 유스케이스들은 그들의 유저 인터페이스 구현으로부터 독립적이여야한다. 또한 그것들은 작아야하고, 잘 정의되있어야한다. 복잡한 앱을 더 작은 유스케이스로 어떻게 쪼갤지 고민하는 것이 과제이고, 이 부분은 숙달이 필요하다. 그러나 당신이 해결할 각 문제나, 당싱이 작성한 각 클래스들의 범위를 제한하는 방법을 사용하면 여러므로 도움이 될 것이다.
VIPER로 앱을 만드는 것은 각 유스케이스를 수행하는 요소의 집합을 구현하는 것을 포함한다. 앱 로직은 유스케이스를 구현하는데 있어서 중요한 부분이지만, 앱 로직만 구현해야하는 것은 아니다. 또한 유스케이스는 유저 인터페이스의 영향을 받을 수 있다. 추가적으로 네트워크나 데이터 퍼시스트와 같은 다른 중요한 요소들과 유스케이스가 어떻게 연결될지 고려하는 것도 중요하다. 요소들은 유스케이스에 플러그인처럼 동작하고, VIPER는 각 요소들의 역할이 무엇인지 그들이 어떻게 서로 상호소통할 수 있는지 표현하는 방법이다.
우리 to-do 앱의 유스케이스나 요구사항 중 하나는 유저 선택을 기반한 여러 방법으로 그룹을 짓는 것이다. 유스케이스에 데이터를 조직화하여, 로직을 쪼갬으로서, 우리는 유저 인터페이스 코드가 깔끔하게 유지되고, 우리가 예상한대로 동작하는지 확인하기 위한 테스트에서 쉽게 유스케이스를 적용할 수 있다.
VIPER의 주요 부분
아래는 VIPER의 주요 부분이다.
- View : Presenter에 의해 요청 받은 것을 화면에 표시하고, 유조의 입력을 Presenter에게 넘겨준다.
- Interactor : 유스케이스에 의해 부여된 비지니스 로직을 담고있다.
- Presenter : (Interactor로부터 받은)content를 화면에 보여주기 위해 준비하거나, (Interactor 로부터 요청한 데이터인) 유저 입력에 반응하여 처리하는 View 로직을 담고 있다.
- Entity : Interactor에 의해 사용되는 기본 모델의 객체를 담는다.
- Routing : 명령에의해 어떤 화면으로 갈지 알고있는 navigation 로직을 담고 있다.
이러한 분리는 단일책임원칙(Single Responsibility Principle)에 들어맞게 된다. Interactor는 비지니스 해석의 임무를 가지고 Presenter는 인터렉션 디자이너를 나타낸다. View는 시각적 디자이너 임무를 맡는다.
아래 다이어그램은 서로 다른 컴포넌트들이 어떤 식으로 연결되는지 보여준다.
VIPER 컴포넌트들은 어떤 순서로도 구현될 수 있지만, 우리는 추천하는 구현 순서대로 소개하고자 한다. 이 순서는 전반적으로 앱을 만드는 과정과 일치한다. 제품에서 이것을 하기 위해 무엇이 필요한지 토론하는 것부터 시작하여, 유저가 어떻게 상호 소통하는지 알려줄 것이다.
Interactor
Interactor는 앱에서 하나의 유스케이스를 의미한다. 이 컴포넌트는 특정 작업을 수행하기 위해 모델 오브젝트(Entity)를 다루는 비즈니스 로직을 가진다. Interactor에서 완료된 일은 어떤 UI에도 독립적이여야한다. 한 Interactor로 iOS앱이나 OSX앱에서 동작될 수 있어야한다.
왜냐하면 Interactor는 로직을 1순위로 담은 PONSO(Plain Old NSObject)이기 때문에, TDD를 이용하여 개발하기 수훨해진다.
샘플 앱에서 첫째 유스케이스는 다가오는 to-do 아이템들을 보여주는 것이다.(예를들어 다음주까지 끝내야하는 어떤것..) 이 유스케이스의 비즈니스 로직은 오늘부터 다음 주말까지 사이에 있는 to-do 아이템을 찾고, 그 기간을 배정하는 것이다.(오늘, 내일, 이번주내, 다음주..)
아래 VTDListInteractor에 해당하는 메소드이다.
Entity
Entity들은 Interactor에 의해 사용되는 모델 오브젝트들이다. Entity들은 오직 Interactor에 의해서만 관리된다. Interactor는 절때로 Presentation 층에 Entity를 넘겨주지 않는다. Entity들 또한 PONSO이다. 만약 CoreData를 사용하면 managed object를 데이터 층 뒤에 남겨두고 싶어 할 것이다. Interactor는 NSManagedObjects와 함께 작업하지 않아야 한다.
여기 이것은 우리의 to-do 아이템을 위한 Entity이다.
Entity가 단순한 데이터 구조체처럼 생겼다고 너무 놀라지 마라. 모든 앱 의존 로직은 Interactor 안에 구현되 있을 것이다.
Presenter
Presenter도 PONSO인데, 이것은 UI에 넘겨주는 로직으로 구성되있다. Presenter는 유저 인터페이스를 언제 주는지 알고 있다. 이것은 유저 인터렉션으로부터 입력을 받고, UI를 갱신할 수 있으며, Interactor에게 요청을 보낼 수도 있다.
만약 유저가 새 to-do 아이템을 추가하기 위해 다른 UI를 present할지 wireframe에게 물어본다.
Presenter는 또한 Interactor에게 결과물을 받고, View에 표시되기 적합한 결과물로 한번 더 변환한다.
아래는 Interactor로부터 최근 아이템을 받은 메소드이다. 이것은 데이터를 처리하고 유저에게 어떻게 보여줄지 결정한다.
Entity는 절때 Interactor에서 Presenter로 넘어가지 않는다. 대신 간단한 데이터 구조체는 Interactor에서 Presenter로 넘어간다. 이러한 것은 Presenter에서 '실제 작업'을 행하는 일을 방지한다. 따라서 Presenter는 View에 띄우기 위한 데이터를 준비하는 것만이 가능하다.
View
View는 수동적인 녀석이다. 이것은 화면에 뿌릴 컨텐츠를 Presenter로부터 받을때까지 기다린다. 절때 Presenter에게 데이터를 달라고 직접 말하지 않는다. (로그인 화면의 LoginView와 같은) View에 정의된 메소드들은 높은 수준의 추상화를 통해 Presenter와 소통할 수 있게 해주며, 어떻게 그 컨텐츠가 화면에 표시되는지에대한 것은 아니다. Presenter는 UILabel, UIButton등등 이런 것들이 존재하는지 조차 모른다. Presenter는 오직 컨텐츠를 들고 있다는 것과, 언제 화면에 표시하는지만 안다. View는 화면에 어떻게 표시할지만 결정한다.
View는 Objective-C 프로토콜(Protocol)로 정의된 추상 클래스이다. UIViewController나 그것의 자식 클래스가 View프로토콜을 구현할 것이다. 우리 예제의 'add' 화면은 아래 인터페이스를 가진다.
View와 View Controller들은 유저 인터렉션과 유저 입력 또한 다룬다. 이것이 왜 View Controller가 커져버리는지의 이유이고, 그들에겐 입력의 몇 동작을 다루는데 가장 쉬운 위치이다. 유저가 어떤 동작을 취할때, View Controller 의존을 유지하기 위해 그것에게 필요한 부분의 정보를 주도록 해야한다. View Controller는 이 동작에 의해 어떠한 결정도 내려선 안되며, 할 수 있는 어떤것 사이에서 이 이벤트를 넘겨줘야 한다.
우리의 예제에서 AddViewController는 아래 interface를 따르는 이벤트 핸들러 요소를 가진다.
유저가 취소버튼을 누르면 View Controller는 유저가 add 동작을 취소했다고 이벤트 핸들러에게 말한다. 여기서는 이벤트 핸들러가 AddViewController를 dismissing 시키고 리스트 뷰를 갱신해라고 알린다.
ReactiveCocoa는 View와 Presenter를 연결해주는 역할을 한다. 이 예제에서 ViewController는 버튼 액션을 나타내는 신호를 반환하기 위해 메소드를 제공할 수도 있다. 이것은 역할을 나눌 필요 없이 Presenter가 쉽게 이 시그널을 응답할 수 있게 해준다.
Routing
한 화면에서 어디로 갈지의 기능은 인터렉션 설계자에의해 Wireframe에 정의되어있다. VIPER에서 Routing의 기능은 Presenter와 Wireframe 이 두 객체가 공유되게 하는 것이다. Wireframe 객체는 UIWindow, UINavigationController, UIViewController 등이 될 수 있다. 이것의 기능은 View나 ViewController를 생성하고 화면에 설치하는 일이다.
그러므로 Presenter는 유저 입력에 반응하는 로직을 가지고, Presenter는 언제 다른 화면으로 어디로 가는지 알고 있어야 한다. 한편 Wireframe은 어떻게 화면간 이동을 하는지 알고 있어야한다. Presenter는 Wireframe을 사용하여 화면 이동을 실행한다. 둘은 어느 화면에서 다른 화면으로 가게 해준다.
Wireframe은 또한 navigation transition animation을 다루는 명확한 위치에 있다. add wireframe인 이 예제를 보자.
Add View Controller를 띄우기 위해서 커스텀 View Controller Transition을 사용한다. 그러므로 Wireframe은 Transition 실행의 역할을 담당한다. 이것은 Add View Controller를 위한 Transition 델리게이트가 되고 이것은 적절한 Transition 애니메이션을 리턴할 수 있다.
VIPER에 맞춰진 앱 컴포넌트
iOS 앱 아키텍처는 앱을 만드는 주 도구가 UIKit과 CocoaTouch라는 사실을 고려할 필요가 있다. 이 아키텍처는 앱의 모든 요소들이 평화롭게 공존해야한다. 또한 프레임워크의 어떤 부분이 사용되는지, 그들이 어디에 있어야하는지 가이드라인을 제공해야 한다.
iOS 앱의 작업장은 UIViewController이다. 이것은 쉽게말해, MVC를 대체하는 그 대상은 View Controller가 무거워지는 것을 최대한 피해야한다. 그러나 View Controller는 플랫폼의 중심에 있다: 이들은 화면회전을 다루고, 유저 입력에 반응하며, navigation controller와 같은 시스템 요소까지 담고 있으며 iOS7부터는 화면 사이에 커스텀 Transition 까지 가능한데, 이 모든것이 View Controller에 들어가게되면 View Controller가 무거워 질 수 밖에 없다.
VIPER에서는 View Controller는 오직 View를 컨트롤하는 역할만을 한다. 우리 to-do 리스트 앱은 2개의 View Controller를 가진다. 하나는 리스트 화면이고 나머지 하나는 추가(add)화면이다. Add View Controller는 극단적으로 기본적인 것만 구현되있다. 왜냐하면 오직 View를 컨트롤 하는게 전부이기 때문이다.
앱들은 보통 네트워크를 탈 때 다소 부자연스러운 면이 있다. 그러나 어디서 네트어크를 타야하고, 네트워크를 타기위해 누가 초기화를 해줘야할까? 이 일은 보통 Interactor에서 일어나되, Interactor가 직접 네트워크를 타는 코드를 가지고 있지는 않는다. 이것은 Network Manager나 API Client같은 의존(dependency)에 물어볼 것이다. Interactor는 유스케이스 실행에 필요한 다양한 정보를 다양한 소스로부터 제공받아 모으는 일을 할 것이다. 그러면 Interactor로부터 Presenter에게 데이터를 넘겨주는데, 화면에 표시하기 적당한 형식에 맞춘다.
Data Store는 Interactor에게 Entity들을 주는 역할을 한다. Interactor는 그것의 비즈니스 로직을 감당하고 있으므로 Data Store로부터 Entity를 검색하고, Entity를 다루고, 그리고 Data Store에 Entity들을 집어넣어 저장하는 기능들이 필요할 것이다. Data Store는 Entity들의 퍼시스트를 관리한다. Entity들은 Data Store에 대해 전혀 모르므로 Entity들은 어떻게 그들 스스로 퍼시스트 되는지도 모른다.
Interactor도 마찬가지로 어떻게 Entity들을 퍼시스트 하는지 몰라야한다. 때때로 Interactor는 Data Store와 함께 그것의 인터렉션을 위해 Data Manager에서 불려진 오브젝트 타입을 사용하고 싶어한다. Data Manager는 패치(fetch) 요청 생성, 쿼리 만들기 등과 같은 Store에 특화된 오퍼레이션 타입을 다룬다. 이러한 것은 Interactor가 앱로직에 더 집중할 수 있게 해주며, 어떻게 Entity들을 모으고 퍼시스트 하는지 몰라도 된다. Data Manager를 사용하는 시점은 CoreData를 사용하는 시점과 같다. 아래 그 설명이 있다.
이것은 앱 Data Manager의 인터페이스 부분이다:
Interactor를 개발하기 위해 TDD를 사용할때, Production Data Store를 테스트 double/mock(옮긴이: 테스트를 위해 만든 가상의 더미코드, 껍데기코드)으로 전환할 수 있다. 원격의 서버를 사용하지 않고, 디스크 접근을 하지 않으면서 테스트 하는 것은 더 빠르고 더 많은 반복 테스트를 해볼 수 있다.
명확한 경계로 별개 층을 만들어 Data Store를 사용하는 이유는 당신으로 하여금 특정 퍼시스트 기술을 고르는데 지연할 수 있게 해준다(옮긴이: ?). 만약 당신의 Data Store가 하나의 클래스로 이루어져 있다면, 기본 퍼시스트 전략부터 앱을 만들어야하고, 그 다음 SQLite나 CoreData를 업그레이드하여 감각대로 만들면 당신의 앱 코드베이스에서 모든것이 바뀌어야 하게 될지도 모른다.
iOS 프로젝트에서 CoreData를 사용하는 것은 종종 아키텍처를 짜는 것 보다 시간이 더 걸릴 수도 있다. 그러나 VIPER와 함께 CoreData를 사용하면 여태껏 겪어보지 못한 최고의 CoreData를 경험 해볼 수 있을 것이다. CoreData는 유지보수의 빠른 접근과 낮은 메모리 접유율로 데이터를 퍼시스트 하는 좋은 툴이다. 그러나 이것은 NSManagedObjectContext가 앱 구현파일의 전체에 걸쳐 휘감아버리는 습관이 있다. VIPER는 Data Store 층에서 이것을 해결해준다.
여기 to-do 예제에서는 CoreData가 사용된다는 것을 아는 부분은 딱 두 부분이다. Core Data 스택을 설정하는 Data Store 자체와 Data Manager 이 둘이다. Data Manager는 패치 요청을 실행하는데, 표준 PONSO 모델 오브젝트로 Data Store에의해 반환된 NSManagedObject들을 변환하고, 이것을 비즈니스 로직 층에 넘겨준다. 이런 방법은 앱의 코어가 Core Data에 절때 의존하지 않게 된다. 추가적으로 불완전한 스레드의 NSManagedObject 동작을 걱정할 필요가 없다. 이것은 CoreData Store 요청을 만들어 낼 때의 Data Manager 내부 모습이다.
대부분의 CoreData 작업은 UI 스토리보드에서 일어난다. 스토리보드는 수많은 유용한 특징들이 있고, 전적으로 실수를 줄여주는 장점이 있다. 그러나 스토리보드의 기능만을 사용하여 작업하기엔 VIPER의 모든 목적을 달성하기 어렵다.
우리가 만들고자 하는 절충안은 segue( https://developer.apple.com/library/ios/recipes/xcode_help-IB_storyboard/Chapters/StoryboardSegue.html )(옮긴이 : 스토리보드에서 드레그 드롭으로 화면간의 이동을 연결해주는 방식)들을 사용하지 않는 방법이다. 우리는 종종 segue들을 사용하여 화면을 만드는 경우가 있을 수 있지만, segue를 사용하면 화면들 사이에–UI와 앱 로직 사이도 마찬가지로–분리를 유지하기가 쉽지 않을 것이다. prepareForSegue 메소드를 필연적으로 구현해야 할 때 최고의 방법은 segue를 쓰지 않으려 노력해야한다.
다른 경우에 스토리보드들은 유저 인터페이스를 위한 레이아웃 구현이 잘 되있다(특히 Auto Layout 같은). 우리는 스토리보드를 이용한 to-do 리스트 화면 구현과 우리 고유 navigation 동작과 같은 것을 코드를 이용한 구현으로 둘 다 채택하였다.
모듈을 만들기 위해 VIPER를 사용하기
VIPER를 사용하는 종종, 한 화면이나 화면들의 집합이 하나의 모듈로 나뉘는 경향을 보게 될 것이다. 한 모듈은 여러 다른 방법으로 구현될 수 있으나, 일반적으로 이러한 방법(화면 단위로 나누는)이 적당할 때가 많다. 팟케스팅 앱에서, 한 모듈은 오디오 플레이어 혹은 구독 브라우저가 될 수 있다. 우리 to-do 리스트 앱에서, list와 add 화면은 각 다른 모듈로 만들어 질 수 있는 것이다.
모듈들을 만듦으로써 앱 설계를 하는것은 몇가지 이점이 있다. 그 중 한가지는 모듈들이 굉장히 깔끔하고 잘 정의된 인터페이스를 가질 수 있으며, 게다가 다른 모듈에게 독립적이다. 이러한 이유로 기능을 넣고 빼기가 쉬우며 여러분의 인터페이스를 유저에게 보여주는 방법도 쉽게 바꿀 수 있다.
우리는 to-do 리스트 예제에서 모듈 사이에 분리를 굉장히 명황하게 하고자 원했고, 우리는 add 모듈을 위해 두개의 protocol을 정의했다. 첫번째는 모듈 인터페이스로서, 모듈이 무엇을 할 수 있는지 정의했다. 두번째는 모듈 델리게이트로서, 모듈이 무엇을 했는지 보여준다. 아래는 예제이다:
그러므로 한 모듈은 유저에게 어떤 의미를 지니도록 보여질(present) 수 있고, 모듈의 Presenter는 보통 모듈 인터페이스를 구현한다. 다른 모듈에서 이것을 보여주고 싶을 때, 그것의 Presenter는 모듈 델리게이트 프로토콜을 구현해야하고, 모듈이 보여질 때 모듈이 무엇을 했는지 알고 있을 것이다.
한 모듈은 여러 화면에서 사용될 수 있는 Entity, Interactor, Managerdls 인 일반적인 앱 로직 층을 가지고 있어야 한다. 물론 이것은 화면들 사이에 인터렉션에 의존하며 비슷하게 생겼다. 쉽게 to-do 리스트를 예로들어 한 모듈은 오직 한 화면으로 표시될 수 있다. 이 경우에는 앱 로직 층이 그것의 각 모듈의 동작에 굉장히 특화될 수 있다.
모듈들은 코드를 조직화하기에 꽤 좋은 방법이다. 만약 당신이 뭔가 바꾸고 싶을때 XCode에서 모든 코드를 그것의 그룹이나 폴더에 집어 넣어 둔다면 다시 찾기 쉬워질 것이다. 그리고 클래스가 어디 있는지 당신이 예상하는 곳에 있게될 것이다.
VIPER에서 모듈화를 시킬때 또 한가지 장점은 여러 형태로 쉽게 확장할 수 있다는 것이다. Interactor층에 고립된 모든 유스케이스의 앱 로직을 가지는 것은 앱 층을 재사용함으로서 테블릿, 폰, 맥의 새 유저 인터페이스를 만드는데 집중할 수 있게 해준다.
한걸음 더 나아가서 아이패드를 위한 유저 인터페이스는 아이폰의 View, View Controller, Presenter를 재사용 할 수 있을 것이다. 이 경우 아이패드 화면은 아이폰에서 쓰인 Presenter와 Wireframe을 'super' 함으로써 표현될 수 있다. 다중 플랫폼을 지원하면서 개발하고 유지보수하는 것은 꽤 도전일 수 있으나, 모델과 앱 층을 재사용하는 좋은 아키텍처라면 이것을 쉽게 도와줄 것이다.
VIPER에서 테스트
이 VIPER는 일을 쪼개도록 도와주는데, 이것이 TDD를 쉽게 적용시키게 만들어준다. Interactor는 어떤 UI에도 독립적인 순수히 로직만을 담고 있는데, 이것은 테스트하기 쉽게 해주는 역할을 한다. Presenter는 화면에 보여주기 위한 데이터를 준비하는 로직을 가지며, 이것은 어떤 UI 위젯에도 독립적이다. 이 로직을 개발하는 것 또한 테스트하는데 쉽게 도와준다.
우리의 필요한 메소드는 Interactor에서 시작될 것이다. UI에서 모든 것들은 유스케이스의 필요한 것을 그들에게 나를 것이다. Interactor API를 테스트하기 위해 TDD를 사용함으로서, 당신은 UI와 유스케이스간의 관계를 더 잘 이해할 수 있게 될 것이다.
이 예제에서, 다가오는 to-do 아이템 리스트를 위한 Interactor의 역할에 대해 살펴볼 것이다. 다가오는 아이템을 찾는 방법은, 우선 다음 주말까지 모든 아이템을 검색하고, 오늘, 내일, 이후 이번주, 다음주 별로 시간에 따라 묶는 것이다.
우리가 작성한 첫번째 테스트는 Interactor가 다음 주말까지의 모든 to-do 아이템을 찾아내는지 확인하는 것이다.
Interactor가 적절한 to-do 아이템을 위해 요청한다는것을 알고, 그것이 정확한 날짜와 연관하여 올바르게 to-do 아이템들을 배치하는지 확인하기 위한 여러 테스트를 만들면 된다.
이제 Interactor의 API가 어떻게 생겼는지 알고 있으므로 Presenter를 만들어 볼 수 있다. Presenter가 Interactor로부터 다가오는 to-do 아이템들을 받게되면 이것이 적절한 양식의 데이터인지 UI에 띄울 수 있는지 테스트하고 싶을 것이다.
또한 유저가 새 to-do 아이템을 추가하기 원할때 앱이 적절한 동작을 취하는지도 테스트하고 싶을 것이다.
이제 View를 개발할 수 있다. 만약 다가오는 to-do 아이템이 하나도 없다면, 특정 메시지를 띄울 필요가 있을 것이다.
화면에 띄울 다가오는 to-do 아이템이 있다면, 테이블이 보여지길 원할 것이다.
먼저 Interactor를 만들면 TDD에 맞추기 편해진다. Presenter에 따라 Interactor부터 먼저 개발하면 이 층 주변에 테스트들을 만들어 낼 수 있고, 이 유스케이스를 구현하기 위한 기반을 설치할 수 있다. 그것들을 테스트하기 위해 UI와 연결될 필요가 없으므로 이 클래스들을 빠르게 재사용할 수 있을 것이다. 그러고 View를 개발할 때 테스트된 로직과 디스플레이될 층을 서로 연결만 시켜주면 되겠다. 모든 테스트가 잘 동작했다면, View 개발을 다 끝냈을 땐 한번에 앱의 모든 기능이 잘 동작함을 확인할 수 있을 것이다.
결론
VIPER 소개에 즐거웠기를 바란다. 많은 사람들이 이제 다음으로 무얼 해야할지 고민하고 있을 것이다. 만약 당신의 다음 앱을 VIPER를 써서 만들고 싶다면, 어디서부터 시작할 수 있을까?
이 글과 VIPER를 사용한 우리 예제는 우리가 만들 수 있는한 최대한으로 명확하고 잘 정의되게 했다. 우리의 to-do 리스트 앱은 꽤 같단하지만서도 어떻게 VIPER를 이용하여 앱을 만드는지 정확하게 설명하고 있다. 실제 프로젝트에서 당신의 과제와 제약사항들이 이 예제와 얼마나 비슷할지는 모르겠다. 우리의 경험에서는 각 프로젝트에서 VIPER를 조금씩 사용하도록 조금씩 바꾸었고, 바꾼 프로젝트 모두 그러한 접근법을 가이드하기위해 그것을 사용하는 것으로부터 좋은 이점을 얻었다.
다양한 이유로 VIPER에 의해 놓인 길로부터 탈선하고 싶은 생각이 들 수 있다. 아마 '토끼(bunny)' 객체를 양토장에 넣거나, 당신의 앱이 스토리보드에서 segue를 사용하고 싶을 수도 있을 것이다. 괜찮다. 이런 경우에는 당신이 어떤 결정을 내리든 VIPER가 어떤 표현을 할지 그 정신만을 기억하면 된다. 그것의 핵심은 VIPER가 단일 책임 원칙(Single Responsibility Principle)을 기반한 아키텍처라는 것이다. 당신에게 뭔가 문제가 생기면 어떤것을 먼저 옮길지 결정할 때 이 원칙을 생각해보아라.
그리고 또한 당신은 이미 존재하는 앱에 어떻게 VIPER를 적용시킬지 고민할 수도 있다. 이 경우, 새 기능이 추가될때 VIPER를 고려하여 추가해보아라. 우리는 이미 존재하는 프로젝트들도 이러한 루틴을 타고 있다. 이렇게 하면 VIPER를 이용한 모듈을 만들 수 있고, 또한 단일 책임 원칙을 기반으로 한 아키텍처는 현재 적용시키기 어려운 이슈들을 해결하는데 도움을 줄 것이다.
소프트웨어를 개발하는데 있어 재미있는점 중 하나는 모든 앱이 각기 다르다는 것이다. 따라서 각기 다른 앱마다 서로 다른 아키텍처를 구현할 수 있다. 우리는 모든 앱마다 새로운 배움의 기회를 얻을 수 있고 새로운 것을 시도한다. 만일 여러분이 VIPER를 사용하기로 결정하면, 몇가지 새로운 어떤 것을 배우게 될 것이라 생각된다. 읽어주어서 감사하다.
Swift 부록
저번주에 애플은 WWDC에서 Cocoa, Cocoa Touch 개발로서 Swift 프로그래밍 언어를 소개했었다. Swift 언어에 대해 복합적인 견해를 만들기에 좀 이르지만, 언어는 소프트웨어를 어떻게 설계하고 만드는지에 가장 큰 영향을 줄 것이라 생각된다. 우리는 VIPER가 무엇인지 배우는데에 돕기위해 Swift를 이용한 VIPER to-do 앱을 새로 만들었다. 우리는 Swift의 몇 특징을 찾았는데, VIPER를 이용해 앱을 만들기에 더 향상된 경험을 할 수 있었다.
Structs
VIPER에서 우리는 각 층 (Presenter에서 View로) 사이에 데이터를 주고 받기 위해 가벼운 모델 클래스를 사용한다. 이 PONSO들은 정말 간단하게 작은 양의 데이터를 다루는 경향이 있고 자식 객체가 되지 않으려는 경향이 있다. Swift 구조체들은 이 상황과 아주 맞아 떨어진다. 여기 VIPER Swift 예제에서 사용된 구조체의 예시가 있다. 이 구조체는 동일한지 체크하는 것이 필요했고, 이 타입의 두 객체를 비교하기 위해 == 연산자를 오버로드 하였다.
Type Safety
Objective-C와 Swift의 가장 큰 차이는 어쩌면 타입을 어떻게 다루는지이다. Objective-C는 다이나믹 타입이고 Swift는 컴파일 시간에 이것이 어떻게 구현되는지 타입체크를 함으로서 굉장히 의도적인(intentionally) 제약을 가진다. VIPER와 같은 아키텍처에는 앱이 여러 층에 걸쳐 구성될 때, type safety가 프로그래머의 효율면이나 아키텍처 구조 측면에서 크게 도움을 줄 수 있다. 컴파일러는 컨테이너를 확신할 수 있게 도와주고 오브젝트는 그들이 각 층 경계를 지나갈 때 옳바른 타입인지 확신할 수 있게 도와준다. 위에서 보여주듯 구조체를 사용하기에 좋은 장소이다. 만약 한 구조체가 두 층 사이에 경계에서 존재해야 한다면, 두 층 사이에서 어딘가로 사라져버리지 않는다는 이점을 얻을 것이다. type safety에 감사하라.
추가적인 읽을거리
iOS 아키텍처 관련 번역글
번역에 도움을 주신 분 :
'Swift와 iOS > 아키텍처' 카테고리의 다른 글
(번역)Real World Flux Architecture on iOS (0) | 2016.10.01 |
---|---|
(번역)Implementing MVVM in iOS with RxSwift (0) | 2016.10.01 |
(번역)Brigade's Experience Using an MVC Alterative (0) | 2016.08.27 |
(번역) 언제 VIPER를 사용할까? (0) | 2016.07.09 |
(번역) iOS 아키텍쳐 패턴들 (스압주의) (8) | 2016.02.15 |
WRITTEN BY
- tucan.dev
개인 iOS 개발, tucan9389
,