[iOS] HighwayInfo: 앱 출시기

zoe·2023년 5월 26일
0

프로젝트 소개

안녕하세요, 최근 3개월동안 개발했던 앱을 출시했습니다.
제가 진행한 프로젝트는 HighwayInfo라는 앱으로, 고속도로 교통정보 및 휴게시설에 대한 정보를 제공합니다.

기획부터 디자인, iOS개발을 하면서 느꼈던 점들을 공유해보고자 합니다.
이번 포스트는 MVVM과 클린아키텍처, RxSwift를 도입한 경험에 대한 이야기입니다.

앱 전체 디자인

MVVM + Clean Architecture + RxSwift 도입

MVVM

새 애플리케이션을 구축할 때 마다 항상 어떤 아키텍처 패턴을 선택해야 하는지 고민이 된다.
나도 어떤 패턴이 좋을지 고민하다가 이전 프로젝트들에서 MVC 패턴의 단점이 크게 와닿아 MVVM 패턴을 적용해보기로 했다.
특히 화면을 그리는 코드와 데이터를 처리하는 코드를 분리 구성하여 앱을 구성했다.

각자의 역할을 정리해보면

  1. 뷰: 화면을 그리는 역할을 담당합니다.
  2. 뷰 모델: 사용자의 입력을 받아 그에 맞는 이벤트를 처리하고, 모델의 read, update, delete을 담당합니다.
  3. 모델: 데이터 구조를 정의합니다.

HighwayInfo에서는

실시간 돌발정보를 가져와 화면을 그리고 새로고침 버튼으로 유저의 입력을 받습니다.

위 화면을 MVVM으로 나누어보면

뷰모델은 실시간 돌발정보를 가져오고 새로고침 버튼이 눌렸을 때 최신 돌발정보를 가져옵니다.
뷰가 무엇인지 또는 뷰가 어떤일을 하는지 알지 못합니다.

는 단순히 유저 인터페이스를 표시하기위한 로직만을 담당하고, 그 외에는 메서드 호출정도의 코드만 존재합니다.

이렇게 했을때의 장점은 뷰와 모델이 서로를 전혀 알지 못하기때문에 독립성을 유지할 수 있다는 것입니다.
독립성을 유지하기 때문에 테스트하기에 용이하다는 장점도 있습니다.

Massive ViewModel

MVVM 패턴을 사용하면서 뷰와 비즈니스 로직을 분리하는것에는 성공했지만,
앱이 커지면서 뷰컨트롤러에 속했던 모든 책임이 이제 뷰모델에 속하는 문제가 발생했습니다.
뷰컨트롤러가 방대해졌던 것 처럼 뷰모델도 Massive ViewModel이 될 여지가 남아있겠죠?

저는 이 문제를 해결하기 위해 CleanArchitectureCoordinator 패턴을 적용해보았습니다.

Clean Architecture

클린아키텍쳐 구성

프레젠테이션 레이어는 ViewModel과 View로 구성됩니다.
뷰모델이 이 계층에 속하는 이유는 기존엔 비즈니스 로직이 뷰모델이 포함되지만, 이제부터는 비즈니스로직을 제외한 화면구현에 필요한 데이터를 포함하기 때문입니다.
는 화면을 그리는 역할, 뷰모델은 화면에 그릴 데이터를 준비하는 역할을 수행하게 되는것이지요.

뷰모델에서 분리된 비즈니스 로직은 UseCase라는 이름으로 도메인에 위치하게 됩니다.
유즈케이스는 데이터를 표현하는 모델을 갱신하고, 비즈니스 로직에 의해 영향을 받게되는 모델이 도메인 계층에 속합니다.

데이터 레이어는 Repository Inplementation로 구성됩니다.
Data에서 외부의 데이터를 가져오는 객체를 레포지토리 라고 합니다.

Repository Interface는 왜필요할까?

클린아키텍처를 공부하면서 들었던 의문은 Repository Protocol이 왜 필요한가? 였습니다.
굳이 왜 프로토콜을 만들어 Domain 레이어에 두고, 실구현한 레파지토리 객체는 Data레이어에 속하는지 궁금했는데요

그 이유는 유닛테스트를 하면서 찾을 수 있었습니다.
결론부터 말하자면 유즈케이스가 레파지토리를 직접 소유하면 안되기 때문에 도메인 레이어에 레파지토리에 대한 인터페이스를 둔것이었습니다.

이렇게 하면

  • 의존성 역전 원칙을 준수하게 되고,
  • 의존성의 방향을 내부로 향하도록 유지하면서도
  • 레파지토리에 요청을 보낼 수 있게 됩니다.

즉, 종속성 반전에 필요한 인터페이스가 필요했던것입니다.

클린아키텍처의 핵심 규칙

클린아키텍쳐의 핵심 규칙은 내부레이어에서 외부레이어의 종속성을 가지지 않는다는 것입니다.
화살표는 바깥에서 안쪽으로만 가리킬 수 있습니다.
사진을 보면 화살표가 모두 안쪽으로 향하는 것을 알수있습니다.

Reactive Programming

사용자와 상호작용하며 그에 따라 화면이 실시간으로 변하기 위해 RxSwift를 사용했습니다.

예를들어, 위 화면은 검색창에 단어를 하나씩 입력할 때마다 관련 검색어들이 자동완성으로 바로바로 제시되는 화면입니다.

새로운 검색어에 대한 검색결과는 아래와 같은 흐름으로 데이터를 받아오게 됩니다.

이를 구현하기 위해 textFiled의 text에 대해 지속적으로 관찰하고, 해당 값이 변할때마다 바로 재연산이 수행되도록 했습니다.
RoadSerivce부터 레파지토리, 유즈케이스, 뷰모델로 이어지는 데이터의 연결통로가 끊어지지 않도록 데이터 바인딩을 구현했습니다.

이러한 비동기 처리를 단순 콜백 함수를 사용할 수도 있지만, 비동기 작업이 연속될 경우 구조를 복잡하게 만들기도 합니다.
따라서 콜백보다는 Observation이 비동기 작업 처리에 효율적이고 더 명확하게 데이터를 처리할 수 있었다고 생각합니다.

Input/Output Modeling

데이터 바인딩을 통해 모델과, 뷰, Input과 Output이 서로서로 데이터를 실시간으로 공유받고 업데이트하도록 구현했습니다.

화면에서 일어나는 이벤트를 Input에 정의하고, 뷰로 넘겨줄 데이터들을 Output에 정의해주었습니다.
transform 메서드로 이벤트나 데이터를 처리해 Output으로 반환하도록 구현했습니다.

뷰 컨트롤러는 Output에 담겨있는 Observable들을 구독해두고
새로운 값이 스트림에 들어올 때 마다 UI를 업데이트 하게됩니다.

이렇게 비동기 처리방식을 하나로 통합하여 코드에 일관성을 주었고 가독성을 높였습니다.

Data Flow

  1. View가 ViewModel의 메서드르 호출합니다.
  2. ViewModel이 UseCase를 실행시킵니다.
  3. UseCase가 데이터를 취합해 비즈니스로직을 수행합니다.
  4. Repository는 각 서비스를 통해 네트워크 통신을 합니다.
  5. 반환된 데이터가 아이템들을 화면에 출력할 뷰에 전달됩니다.

클린아키텍처를 통해 각 모듈들의 역할을 분리하고, RxSwift를 통해 데이터를 뷰에 바인딩하여 화면을 업데이트 할 수 있었습니다.

마무리

이번 프로젝트를 하면서 기획부터 디자인, iOS개발 모두 혼자 해야해서 힘들었지만, 그만큼 배운점도 많았다.

RxSwift, CleanArchitecture, Coordinator 모두 처음 접하는 개념들이라 목표했던 출시일보다 2주정도 늦어진게 좀 아쉽긴 하다. 특히 RxSwift의 단점이 러닝커브 높음 인 만큼 익숙해지는데 시간이 꽤 걸렸던 것 같다.

코드를 잘 짜는것 외에도 저작권이나 문구들, 디자인, 실행속도 등 신경쓸게 꽤 많았다. 야곰아카데미에서는 요구사항을 제공해주기 때문에 그에 맞게 개발하면 되는데, 이번에는 요구사항을 구체적으로 명시해놓지 않아서 자꾸 방향을 잃기도 했다.

앞으로 피드백 받으면서 디자인, 사용성 측면에서 더 보완할 부분을 찾아서 리팩토링 해야겠다.

프로젝트 보러가기

https://github.com/na-young-kwon/HighwayInfo

profile
개발하면서 마주친 문제들을 정리하는 공간입니다.

0개의 댓글