UIKit + MVC 에서 SwiftUI + MVVM 으로 살짝(?) 전환 해보기
최근 짧고 굵게 SI 프로젝트에 참여할 기회가 있었다. 간만에 참여한 프로젝트의 철수 시점에 조금 여유(!)가 생기고, 또 우연치 않게(?) 철수 전에 iOS14 정식 릴리즈가 된 기념으로, 내가 맡은 파트를 SwiftUI 로 전환해보는 작업을 진행하였다.
배경 설명
내가 담당했던 화면은 조회 중심의 기능을 가지고 있었으며, 최소 3개의 API를 호출하여 받아온 정보를 기반으로 화면의 이곳 저곳에 UI 구성 및 정보를 보여주어야 하는 형태였다.
구현시에는 ‘만약의 사태’에 대비해 MVC 를 열심히 지키려고 노력했고, 결과적으로 좋은 선택이었다.
1. View : 화면 구성 및 디자인적 요소는 최대한 Storyboard 에서 처리하고, 코드를 통한 디자인 적용은 최소한으로 하기
— 코드는 가급적 데이터와 UI 사이의 ‘연결’에만 집중하기
— embed 를 이용해서 열심히 쪼개고 쪼개기
2. Model : API 당 Model 은 무조건 하나씩 만들어서 구성을 하고, 전체적으로 묶어서 관리할 수 있도록 Interface 를 Protocol 로 만들기
— 프로토콜의 선언은 프로세스의 통일을 가져오게 됨.
— 프로토콜은 타입으로 사용할 수 있으므로, 코드가 ‘몰리는 지점’에서 효과적인 대응이 가능함 (ex. -prepareForSegue:sender:)
3. 파일은 extension이 적용되면 무조건 별도의 파일로 만들기
— 성능 좋아진 Xcode 라고 해도, Swift 단일 파일 크기가 커지면 느려지니, ‘점프’는 좀 하더라도 파일은 열심히 쪼개는 방향으로 가게 됨
등등…
그 외에 localDB 접근 방식과 그렇게 선택한 이유들이 있지만, 이 부분은 논외 이므로… (자세한 내용은 생략한다!)
SwiftUI 로 가는 길
1. 첫 화면 만들기
기존 프로젝트에서 SwiftUI 를 구성하려면 UIHostingController 를 상속받아서 Entry Point 를 구성해야 한다. UI Entry Point 가 Storyboard 안에서 Initial Segue이었으므로 큰 문제 없이 Scene 추가 후 기존 MainViewController 를 교체하면 되었다.
하지만, 정작 문제는 SwiftUI Preview 였는데…
작업 당시 제공받은 하드웨어가 성능이 그리 좋지 않았고, 이런 저런 이유로 클린 빌드가 10분(!) 정도 소요되는 상황이었으므로, SwiftUI Preview 가 제대로 동작하지 못하고 있었다. 잠시 고민 후, 별도의 테스트 프로젝트 구성 후 그곳에서 UI 를 만든 뒤, 어느정도 완성 후 메인 프로젝트로 적용하기로 결정, State 기반의 UI 가 만들어진 이후에 UIHostingController 를 통한 적용을 시도하였다.
2. 안녕~ 난 버전 오류 라고 해~
가즈아!!를 외치며 SwiftUI 파일들을 뭉텅이(?)로 기존 플젝에 밀어넣자마자, 기다렸다는 듯 몰려오는 iOS 버전 오류들!! 그렇다, 이 프로젝트는 iOS9 부터 지원하는 프로젝트였던 거시다. ㅎㅎ
잽싸게 available 태그들을 쭈우욱 달기 시작했다. 그 와중에 LazyGrid 적용 덕분에 신나 있다가, iOS14 만 지원되는걸 깨닫고 잠시 멘붕… (그때만해도, iOS14 지원 테스트 디바이스 확보가 안되었.. 쿨럭;;) 암튼 구간들을 정리하고 다시 컴파일을 했다.
2. State 에서 StateObject, Environment 를 거쳐 Publisher 를 위한 MVVM 까지
단계별 적용을 하기 위해서, ‘그나마’ 간단해보이는 API 하나를 선택했다.
데이터를 바인딩 해보기 위해서 StateObject로 모델을 바로 선언, 서브뷰들의 State 자료형들을 네트워크의 response 객체들로 전환했다.
response 객체들 중, id 가 필요한 케이스들은 결과를 바로 뿌려보기 위해서, Identifiable protocol을 적용하였다. 나의 앞길을 가로막는(??!!) id 들은 UUID 생성으로 막 우기기.. (흠흠)
두근 두근. 설레는 맘으로 CMD + R
앗, 잠깐.. 근데 데이터가 안보이네?!
그렇다, 열심히 타입들은 맞춰놓고, 정작 서버에서 값을 받아오지 않았다. (사실 이전 테스트때 — SwiftUI 전환 전에 — 받아서 로컬에 담아놓기는 했다.. 신선한 데이터가 없어서 그렇지..)
잽싸게 onAppear modifier 를 추가해주고, 바로 StateObject 로 잡아둔 API Model 에 request 를 던졌다.
오오.. 서버로 리퀘스트를 하고, 응답 결과를 업데이트 해준다!!
근데… 여러군데에서 값을 binding 해야 하고, 데이터 request 도 사용자가 해주거나 해야 하는데…..?!
3. ViewModel 만들기
기존에 존재했던 징검다리, ViewController 를 대신해서 작업을 해줄 ViewModel 을 구현하기 시작했다.
일단, 새로 추가된 클래스는 Obserable protocol 을 만족하게 구성하였다.
기존에 StateObject 으로 관리되던 API Model 은 새로 추가된 ViewModel 의 member 로 재구성 되었고, API Model 의 response 는 기존 ViewController 의 멤버변수 처럼 ViewModel 의 멤버변수가 되었다. 외부의 바인딩을 위해서 멤버 변수는 바로 Publisher 로 wrapper 를 씌운 형태가 되었다.
추가된 ViewModel 의 인스턴스는 StateObject 로 관리되다가 서브 뷰에서 바로 찾아 쓸 수 있게 Environment 에 등록해서 사용하게 구성이 되었다.
결국, 큰 수정 없이 ‘간단히’ MVC 기반에서 MVVM 기반으로, UIKit 에서 SwiftUI 로 앱을 구성할 수 있었다.
어때요, 참 쉽죠?! (응?!)
4. 계속 구현은 못했지만…
일정이 맞지 않아서 그 뒤에는 더 구현을 이어갈 수는 없었다.
내가 생각했던 다음 단계는, 나머지 API Model 들을 모두 하나의 ViewModel 의 멤버로 묶은 다음, 각 API response 들을 ViewModel 의 publisher wrapper 로 잡은 뒤, 구성을 해주는 것이었다.
그당시 구성을 고려할 때, ViewModel 을 늘리는게 좋을까, 하나만 가지고 적용하는게 맞을까 고민을 했는데, 지금 시점에서 생각해보면, 하나의 ViewModel 에서 3개의 publisher 를 가진 상태로 구성한 다음, 셋을 관리하기 위한 관점으로 Combine 을 이용해 셋을 묶어서 CombineLatest 로 subscriber 를 구성해도 좋았을 듯 싶다. (아직 컴바인을 제대로 이해한 건 아니라서…. 음..) 만약 구성이 예상대로 이루어졌다면, 기존의 Network Manager 를 대체할 수 있지 않을까 생각해본다.
5. 일단 짧게 경험을 해보니
기존 구현을 모두 SwiftUI 로 전환한 것은 아니지만, 많은 코드가 줄어들고 있었다.
UI Flow도 모두 SwiftUI로 가게 되니, ‘데이터’에 집중하는 코드가 생성되고 있었다. 흥미로운 점은, 과거 XIB(NIB)에서 Storyboard로 넘어가던 시절, Segue 로 ViewController 가 연결되면서 화면 전환 코드가 사라지던, 그 시절의 느낌이 다가왔다.
아.. 여기서 한걸음 더 가는 구나..
덧붙여 초기 로딩은 기존 Storyboard 보다 빠르게 보였다. 코드 컴파일이라 당연한 거긴 하지만…
물론, 화면 구성이 파일 단위로 쪼개지고, 애플은 또 그걸 적극 권장하지만, 이 전체를 한눈에 바라보고 정리할 수 있는 방법을 고민하지 않으면, 관리의 측면에서 많이 고생할 듯 싶다. 이런면에서는 직관적인 Storyboard 가 역시 좋다.
여담으로 (나만 그런듯..ㅠ.ㅜ) 초기 개념 잡기에 시간이 좀 많이 걸렸다.
특히, Stateless 에 대한 이해, 그리고 여기에서 다루지는 않았지만, 사람잡는(?) GeoMetry! 그리고 Combine 까지… (Data-Driven 을 봐야 하나..)
지금 시점에서는 Stateless 이론 탑재 후, 이를 지속적으로 바라보는 Stream 으로 개념 확장과 함께 Combine 을 익혀가면 어떨까 싶다.
Winter is coming!
아직 SwiftUI 를 잘 모르고, Combine 은 더 어렵지만, 틈틈히 시도를 해야 할 걸로 보인다. 작년(2019) 6월에 산호세에서는 2년안에 익숙해져야 할 것이야.. 라는 분위기 였는데 말이다..
근데 뭐 새 위젯은 이미 SwiftUI 로 만들어야만 하니 뭐…
(글고보니 내년 6월이면 벌써 애플이 SwiftUI 발표한 지 2년차 라는거…. 쿨럭;;)