티스토리 뷰
iOS 아키텍처 패턴, 디자인 패턴에 대해 알아보던 중 VIPER 패턴에 대해 공부할 일이 있어서 겸사겸사 블로그에 정리해보려고 합니다.
Apple's MVC
좌측이 Classic MVC 우측이 apple's MVC인데,
iOS 앱 개발 관점에서는 ViewController가 View의 LifeCycle과 깊게 연관되어 있어 View와 Controller의 책임을 나누기가 어렵습니다.
그래서 우측과 같은 애플만의 MVC 패턴이 나오게 되었는데 이 경우 ViewController의 사이즈가 너무 커져서 재사용성이 떨어지고 테스트 관점에서 볼 때도 모델이야 따로 떨어져 있으니 테스트가 된다고 쳐도 View와 Controller를 각각 따로 테스트하기가 어려워집니다.
익숙한 패턴이라 개발 속도도 빠르고 새로운 개발자가 투입돼도 패턴에 대한 적응기간이 불필요해서 좋은 점도 있지만 뭐 하나 바꾸려고 하면 엮인 게 많아서 실수할 여지도 많은 패턴인 것 같네요.
이런 문제점을 해결하고자 MV(X)류의 디자인 패턴들이 많이 나와있지만 MVVM 같은 경우는 binding 때문에 RxSwift를 써야 제대로 구현되는 등 제한점이 많아서 VIPER에 대해 알아보게 되었습니다.
VIPER
기본적으로 VIPER는 책임 분리의 원칙(SRP)을 기반으로 하는데, SRP는 작성된 클래스는 하나의 기능만 가지며 클래스가 제공하는 모든 서비스는 그 하나의 책임(기능)을 수행하는 데 집중되어 있어야 한다는 원칙인데, 간단히 말해서 응집도는 높고 결합도는 낮은 것을 의미합니다.
그래서 책임 별로 각 구성요소를 나눠놨는데 하나씩 살펴보겠습니다.
View
View는 ViewController를 의미하며 UI와 관련된 처리만 합니다.
Presenter에 대한 의존성을 가지고 있으며, viewDidLoad 같은 LifeCycle과 button touch 등의 Event에 대한 처리를 Presenter에게 위임하고 Presenter의 요청을 받아 UI를 Update 합니다.
말 그대로 UI만 담당한다고 보면 됩니다.
Presenter
Presenter는 View, Router, Interactor에 대한 의존성을 가지고 있으며,
View에서 Event를 전달받아 Interactor를 통해 처리하고 View에 data와 함께 UI Update 요청을 보내거나 Router를 통한 화면 이동을 처리합니다.
Interactor
Business 로직을 담당하며, API 통신 등의 Networking이나 Entity(data)에 대한 처리를 하고 결과를 Presenter에 전달합니다.
Router(WireFrame)
WireFrame 이라고도 불리며 화면 전환과 각 구성요소에 DI(의존성 주입)를 처리합니다.
Entity
속성들을 가지고 있는 Data Model을 의미합니다.
이렇게 각각 보니까 뭐가 어떻게 처리된다는 건지 모르겠어서 간단한 예제를 통해 flow를 살펴보겠습니다.
프로젝트 구조인데요, 저는 기존에 만들었던 앱에서 하나의 화면(Login 부분)만 VIPER로 컨버팅 했는데 파일이 6개가 필요했습니다.
책임 분리를 세분화한 패턴이기 때문에 그에 따라 파일이 많아지는 게 단점이기도 합니다.
저는 storyBoard가 아닌 XIB를 사용하고 있기 때문에 AppDelegate의 didFinishLaunchingWithOptions에서 rootViewController를 지정해줬습니다. 그러면서 LoginMainWireFrame(Router)에서 createLoginModule 메서드를 호출했는데
DI를 처리하는 메서드이며, 해당 화면의 각 구성요소에 필요한 의존성을 전부 주입하고 마지막으로 View를 반환합니다.
각 구성요소의 인스턴스 변수의 타입이 프로토콜로 되어있는데 해당 프로토콜을 준수해야 하기 때문입니다.
예를 들어 View는 Presenter에 대한 의존성이 있기 때문에 아래의 변수를 통해 Presenter에 접근합니다.
이 경우 LoginMainPresenter Protocol에 View에서 호출할 메서드가 정의되어 있어야 합니다.
이처럼 요소간의 통신이 일종의 delegate처럼 동작하기 때문에 호출해야 하는 메서드들을 프로토콜에 작성하고 해당 타입으로 변수를 만들어 준 것입니다.
다시 돌아와서 didFinishLaunchingWithOptions Push 된 LoginMainViewController의 View에서는 LifeCycle에 따라 프레젠터에 상태를 전달합니다.
프레젠터는 상태를 전달받은 후 View에 UI Update 요청을 보내는데 여기서는 api통신이 필요 없기 때문에 바로 View의 업데이트 메서드를 호출하지만 api통신 후 값을 받아와서 세팅해야 한다면 인터렉터에게 data를 요청하는 코드를 넣어주면 됩니다.
View의 UI세팅이 완료된 후 id와 password를 입력하고 로그인 버튼을 눌렀다고 가정해보겠습니다.
입력한 id와 password를 파라미터 변수에 저장하고 프레젠터에게 파라미터와 함께 이벤트를 전달합니다.
프레젠터에서는 받은 파라미터와 함께 인터랙터에게 로그인 요청을 보냅니다.
api 통신 결과에 따라서 프레젠터의 메서드를 호출해줍니다. Fail의 경우에는 FailMessage를 전달해서 화면에 띄워주는데, 여기서는 Success가 떨어졌다고 가정해 보겠습니다.
다음 화면으로 이동하기 위해 WireFrame(Router)의 화면 전환 메서드를 호출하며, 전달받은 핸드폰 번호와 현재 View의 인스턴스를 파라미터로 전달합니다.
라우터에서는 다음 화면인 PhoneAuthViewController의 WireFrame을 통해 CreatePhoneAuthModule 메서드를 호출하여 의존성을 주입한 후 리턴된 View를 전달받아 Push 합니다.
이런 식으로 기능별 구성요소를 나눠놓은 패턴이 VIPER 패턴인데 설명하기도 복잡하고 이해하기도 어렵습니다.
정말 큰 프로젝트라면 이렇게 개발했을 때 유지보수하기가 좋고, MVC와 다르게 기능별 Unit 테스트가 쉬워지는 건 맞는 것 같은데
인터페이스가 많아지니 코드 양도 늘어나고 개발 속도도 느려지는 등 저는 개인 프로젝트에 사용하지 않을 것 같네요.
그래도 이러한 패턴을 공부하면서 개발할 때 무엇이 중요하고 어떤 패러다임으로 접근해야 하는지 많은 고민을 하게 되니 좀 더 성장한 것 같아서 기부니가 좋습니다.
'iOS' 카테고리의 다른 글
[iOS] NSCache를 이용한 이미지 캐싱 (0) | 2021.07.12 |
---|---|
iOS 아키텍처 패턴 MVVM (0) | 2021.06.22 |
[iOS] 비동기 API 호출 UnitTest 하는법 (0) | 2021.06.21 |
[iOS] Authenticator 프로토콜로 JWT 인증 구현하기 (0) | 2021.06.07 |
[iOS] ObjectMapper 와 codable 차이 (0) | 2020.08.08 |
- Total
- Today
- Yesterday
- wrappedValue
- UserDefault
- @propertyWrapper
- ios image caching
- ios 이미지캐싱
- ios cache
- ios 캐시
- swift cache
- ios 패턴
- 프로퍼티래퍼
- api호출 unittest
- swift 캐싱
- rxswift안쓰고
- NSCache
- UserDefaultKey
- ios expectation
- MVVM패턴
- rxswift 없이
- 연산프로퍼티
- ios 캐시메모리
- swift mvvm
- 비동기 유닛테스트
- ios memory
- 아키텍처패턴
- SWIFT
- UserDefaults
- inputoutput패턴
- ios 유닛테스트
- ios 디자인패턴
- 앱개발 디자인패턴
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |