들어가며 (공부하게 된 동기)
학교에서 전공 수업을 들었을 때, 학교 밖에서 프로젝트에 참여했을 때, 혼자서 개발 공부를 할 때 어디서든 늘 듣고 강조 받았던 것은 객체 지향적인 설계와 안정적인 프로젝트, 이를 도와주는(튼튼하게 만들어주는) 아키텍쳐였다.
iOS 개발 공부를 시작하고 3개월이 지난 후 아키텍처에 대한 흥미와 배움에 대한 욕구가 생겼는데, 우선 가장 기본이라고 하는 MVC 구조를 제대로 공부해보고 직접 구현해보고 싶다는 마음이 들었다. 지금까지 내가 짜왔던 코드가 바로 MVC 구조인 것이라는 말을 듣기는 했지만 우선 그런 생각을 해본 적이 없었고(그냥 무지성으로 돌아가게만 짰다 😅) 제대로 그 구조를 지켜서 만들어보고 싶었기 때문이다.
의식적으로 MVC 구조를 지키며 코드를 작성하는 것은 생각보다 어려웠다. UIKit 프레임워크는 ViewController 라는 클래스를 제공하는데, 뷰를 그리기 위해서는 이를 필수적으로 사용해야 한다. 하지만 ViewController에서는 뷰를 그리는 것 (= View 역할) 만이 아닌 사용자 이벤트 처리와 데이터 전달 및 가공과 같은 기능도 수행하고 있기 때문에, 이미 View, Controller의 역할을 동시에 하고 있으면서 Model의 영역까지 건드리게 되어서 직선 구조가 아닌 역방향으로 왔다 갔다 하는 상황이 생길 수 밖에 없다. Massive ViewController가 된다.
여러 가지 고민을 했던 것 같다. 내가 이해한 swift 언어가 가진 가장 큰 특징은 확장성 (extension, protocol화)과 delegate 패턴이라고 생각했기 때문에 이를 활용해서 MVC 패턴을 적용하려고 했던 것 같다.
그 결과 로그인, 회원가입과 같은 간단한 기능을 구현할 때는 쉽고 빨랐으며, 나름대로 깔끔한 로직을 짰다는 느낌을 받았다. 하지만 보통의 앱은 로그인과 회원가입에서 화면이 끝나지 않는다.
위는 내가 구현을 맡은 화면이다.
처음으로 봤을 때는 컬렉션 뷰나 테이블 뷰를 사용하여 구현하면 되겠군 이라는 생각이 들 것이고 → 조금 더 고민해본다면 하나의 컬렉션 뷰 셀을 재사용하여 상황에 맞게 (waiting/done → 약/친구) 바꾸거나 숨겨주면 되겠다는 생각이 들 것이다.
하지만 화면에 보이는 리스트에 대한 데이터의 종류가 네 가지인데도 MVC 패턴으로 구현하게 된다면 서버 통신을을 처리하는 부분, 뷰를 그리는 부분을 다른 파일로 따로 빼도 뷰컨 코드의 로직 부분만 200줄이 넘어가는 상황이 발생한다… (이렇게 되면 이슈 발생 시 수정하는 것이 상당히 까다로워진다)(경험담 맞다… 🥹)
MVVM 개념
Model
View
ViewModel (= 뷰와 관련된 모델)
모델과 뷰는 익숙한데, 뷰모델은 생소하다. 뷰와 관련된 모델을 뜻하는 것은 알겠는데 정확히 무슨 역할을 하는 것일까?
뷰모델은 데이터의 상태를 가진다. 즉 데이터를 가지고 있다.
View가 화면이라면 Model은 화면에 그려지기 위한 데이터에 해당된다.
모델은 상황에 따라 다르게 변형되기 때문에, 여러 가지 형태로 구성될 수 있다.
모델의 변환 흐름 (iOS Application을 개발하는 상황이라고 가정)
- JSON: 서버에서 가져올 때의 상태
- struct: 서버 Model (= 내가 swift 언어로 변환한 구조체에 해당)
- String → Date: Model 데이터 (= 실제 데이터)
- String: 화면 Model (= 화면에 보여주기 위한 형태)
여기서 화면용 모델을 ViewModel 이라고 하는 것이고, 서버 Model, DB Model 등 로직에서 필요한 모델을 묶어 Entity로 부르는 것이다.
뷰컨트롤러가 데이터를 빼고/추가하는 것이 아닌, 뷰모델이 데이터의 상태를 가지고 있으면 뷰컨트롤러는 이를 묶어주기만 한다. (뷰컨트롤러에서 빈껍데기 배열을 가지고 있으면 → 뷰모델의 데이터와 연동(binding) ⇒ 뷰모델 안에서 데이터 업데이트와 뷰 랜더링이 함께 이루어지는 것.)
즉 뷰컨트롤러는 뷰모델이 가진 데이터를 서로 연결시켜 주기만 하고 뷰모델에서 로직들을 처리하게 된다.
UIKit에서 뷰모델의 사용을 위해 데이터를 묶어주는 방법을 바인딩이라고 하는데, 이를 위한 여러 방식(= 데이터의 변경을 뷰에 알려주는 방법)으로 call-back, RxSwift/Combine 같은 라이브러리가 사용되는 것이다.
이름만 다를 뿐 의미는 비슷하다. 뷰모델의 데이터가 변경됨을 감지하는 것을 RxSwift에서는 Obserberble이라고 하고, Combine에서는 Publisher라고 한다.
클린아키텍처 구조에서 바라본 MVVM
Entity (원천 데이터, 서버로부터 받아온 그대로의 상태)
Repository (본 데이터, 해독(Decode)된 상태)
Model (근본 데이터)
Service (위를 fetch해서 가져옴)
ViewModel (화면에 보여지기 위해 변환된 최종 타입, 업데이트 감지(call-back) 및 데이터를 뷰에 세팅)
View (그려진 화면)
Entity → Repository → Model → Service → ViewModel → View의 일직선 구조.
여기서 Service, Model 중심으로 화면을 그렸던 (= 양방향성) MVC 구조와의 차이점을 확인할 수 있다.
느낀점
역할에 맞게 코드를 분리하는 것이 클린 아키텍처의 목적이라는 것, 클린아키텍처의 구현 방식들 중 하나로 MVVM, RIBs, VIPER 등이 있다는 것을 알게 되었다.
또한 저번 학기 아키텍처 스터디에서 바이퍼 구조를 적용한 프로젝트를 따라 만들어보며 왜 이렇게 많이 만들고, 나누는 것일까? 하고 들었던 의문점이 해소되기도 했다.
그동간 프로그램이 돌아가는 것, 구현하기만 하는 것에 집중을 하여 코드를 짰었다면, 각 코드의 역할에 맞게 파일로 나누어 조금 더 객체지향적이고 공학적인 방식으로 코딩을 할 수 있을 것이라고 느껴져 재미있었다. 그동안 삽질을 했다고 생각했는데 그래도 이런 시간이 없었다면 필요성을 제대로 느낄 수 없었을 것이라는 생각이 든다.
결론
뷰모델은 뷰와 관련한 데이터의 상태를 가지고 있으며, 뷰컨트롤러 및 뷰와 연결되어 있어 데이터들이 바뀌도록 처리하는 역할을 맡는다.
RxSwift, Combine과 같은 라이브러리를 사용하면 이를 더 쉽게 사용할 수 있다. ex) tableView와 바로 연동
'개발 > iOS' 카테고리의 다른 글
[iOS/Swift] 초기화와 상속 (2) | 2022.05.02 |
---|---|
[iOS] TabBarItem으로 들어간 이미지가 제대로 표시되지 않을 때 (0) | 2022.04.24 |
[iOS] 서브뷰와 addSubView (0) | 2022.04.14 |
[iOS] Carousel 구현 아이디어 (0) | 2022.03.24 |
[iOS] IQKeyboardManager (0) | 2022.03.24 |
댓글