SwiftUI-MVVM
SwiftUI 도입
요즘 프로젝트에 SwiftUI를 도입하면서 어떤 아키텍쳐가 좋을지 고민하고 있다.
공부하다 보니 MVVM 얘기가 많았는데, 보통 많은 iOS 개발자들이 MVVM으로 구현하고 있으므로 SwiftUI에서 동일하게 적용하도록 많이 노력한 것 같다는 생각을 했다.
주로 ObservableObject로 VM을 주입받아서 뷰를 바인딩하는 느낌이었다. 그리고 MVVM View가 VM을 가지고 있는데 왜 MVVM-VM을 만드냐는 얘기가 인상적이었다.
SwiftUI에서는 무형의 Worker 객체가 아닌 뷰 단위를 잘게 쪼개어 재사용하도록 권장하는 느낌이 들었다. 진입장벽을 낮추는 데는 좋지만 글쎄...그래도 원하는 바를 다 이루기에는 한계가 있어 보였다.
아키텍쳐 고민
생각해 보면 나는 아주 오래된 MVC 기반 레거시 프로젝트 1번 이후에는 ReactorKit을 포함하여 프로젝트 아키텍쳐를 구성할때 VM을 두는 방향으로 있는데, 개인적으로는 만족했다. 적당히 뷰가 멍청하고, 뷰모델은 Testable하고. 그런데 결국 VM이 커지게 되고 이 커진 VM을 재사용 관점에서 감당할 수가 없었다. 그리고 VM이 있으니까 View 대신 여기서 비즈니스 로직을 돌려도 된다는 안도감이 좀 있어서 결과적으로 중복 로직을 작성하게 되었다. 분명히 뒤져보면 다른 VM에 비슷한 동작이 있었을 텐데..하면서 재사용에 대한 의문이 항상 있었다.
이후 VIP도 고민해봤을때 충분히 좋은 아키텍쳐인 것 같았는데, 뷰를 추상화시키는데 있어 당장 피쳐 하나 치는데 보일러플레이트가 좀 많고 작업속도가 좀 안 나오는 느낌이었다. 그리고 이미 MVVM-C로 코디네이터를 붙여 사용하고 있어서 화면전환 로직이 덜어내지기도 했고...그리고 근본적으로 Interactor에서의 중복로직은 동일하게 남을 것 같았다.
결국엔 파편화된 중복로직을 담아놓을객체가 필요했고, 보통 Usecase라는 네이밍을 가지게 되었다. 재사용을 위해 순수함수로 입력값과 반환값을 무조건 둬서 Testable하도록 했던 것 같다.
MVVMVM?
SwftUI로 돌아오면 나 역시도 SwiftUI에서 ViewModel 객체를 두고 그대로 바인딩해서 추상화시키는것은 회의적이다. 결국 바뀌는 값들을 알려면 @Binding 값을 어딘가에서 Combine으로 구독하거나 onChange를 붙여야 하는데, Combine을 사용한다면 뷰 바인딩을 어디서 관리할 것이며, onChanged를 붙인다면 화면이 복잡해지면 복잡해질수록 onChanged투성이가 될 것이다.
그리고 SwiftUI에서는 프로퍼티 래퍼 중 뷰의 프로퍼티가 아니면 동작하지 않는 것들이 있다. 예를 들어 @FocusState 어노테이션이 붙은 멤버를 Presenter에서 관리하려고 했는데, 뷰가 아니면 동작하지 않게 되어있었다. SwiftData를 잠깐 써봤을때 데이터를 Fetch하는 부분도 마찬가지로 뷰에 붙어있어야만 동작했다. 이건 프레임워크 구현상의 한계로 아키텍쳐로 풀어나갈 수 없을 것 같았다.
그렇다고 이런 것들을 다시 View로 가져오고, 나머지 것들을 Presenter나 Interactor, VM등으로 가져가면 아키텍쳐를 사용하는 의미가 없다.
SwiftUI
그렇다고 모든 동작을 View에서 돌릴 수는 없다. 우리는 결국 책임을 나눠서 중복 로직을 줄이고 Testable한 코드를 작성해야 한다.
일단 지금으로서는 View 자체를 보통 지금까지 생각해오던 View+VM으로 보고 로직 분리에 Usecase를 가져오기로 결론내렸다. 최대한 비즈니스 로직을 위임하고 입력값과 반환값을 가지는 순수함수로 작성했다.
테스트 코드를 작성하게 되면 View 자체보다는 Usecase별로 작성하게 된다. 재사용 단위도 VM이 아닌 Usecase별에 집중하게 된다.
보통 사용자 Interaction으로부터 동작하는 것들은 구현하기가 쉬웠는데, 그렇지 않고 네트워크 응답이 도착하거나 타이머 등 비동기적으로 동작하는 부분이 좀 까다로웠다. 이 부분에서 Swift Concurrency의 도움을 많이 받았던 것 같다.