13. Why MVVM?
기존MVC 패턴의 문제
-Massive UIViewController is hell!
1000줄이 넘어가는 UIViewController
- UITableViewDelegate, UITableViewDataSource, Life cycle
- UI init methods, Model logic, UI interactions, Flow logic etc.
→MVVM을통해Complexity↓,Testability↑
- Separate business logic from UI logic
44. Why Rx?
비동기로직, 복잡한상황을 좀 더간결하게표현
-No more complex callbacks
Rx로비동기 로직 통일
- No more AsyncTask,GCD, NSOperation, Thread
메이저비동기 오픈소스들이 Rx 지원
- Alarmofire, Moya, Realm
오늘은 세 가지 주제를 가지고 얘기를 하려고 합니다. 오늘은 Rx 이야기를 많이 하게 될 것 같아, MVVM을 상대적으로 적게 먼저 다루고, 이후 Rx와 연관된 이야기를 계속 할 것 같습니다.
제 소개를 드리자면, 대학교 3학년 때부터 대구에서 Yooii Studios라는 스타트업에서 창업멤버로 5년 정도 있으면서 5개 앱의 Lead Programmer, Project Manager를 담당했고, 2개 정도의 앱을 서포트했습니다.
그리고 책을 워낙 좋아해 도서관에 엄청 자주 드나들었는데, 모바일에서 도서관 서비스를 사용하기 너무 불편해 졸업 프로젝트로 시작해 수업 이후 상용화해 4년 정도 업데이트를 했고, 안드로이드도 교수님 요청으로 후배들과 같이 개발해 2년 정도 업데이트를 했습니다.
현재는 BBB라는 헬스케어 회사에서 구글 플레이/앱스토어용 앱을 주로 개발하고 있습니다.
유틸리티 앱들과 사진 관련 엔터테인먼트 앱을 만들었구요,
도서관 앱입니다. iOS 7이었나요 Flat UI가 나왔을 때 맞춰서 업데이트를 했습니다.
안드로이드는 Material design guide에 맞춰서 작업했습니다.
학생 때부터 시작해 Android 개발을 더 많이 했구요, 다만 iOS 개발을 훨씬 좋아합니다. 개인적으로 양 플랫폼에서 도서관앱을 서비스했기 때문에 새 디자인, 기술이 나오면 여기서 적용하려고 노력하며 한 쪽 기술이 뒤쳐지지 않으려 노력했습니다.
오늘 얘기할 내용들도 비슷한 계층의 기술들을 Android에서 시행착오를 겪어가며 먼저 경험했고 iOS쪽으로 넘어 오면서 조금 더 익숙해 질 수 있었던 것들입니다.
오늘 발표는 Swift 3.0 기준으로 하려고 합니다. 주변 개발자분들 얘기를 들어 보면 아직 레거시 때문에 Objective-C를 쓰거나, Swift 2.3을 쓰는 경우가 있지만, 오픈소스들도 대부분 3.0 기준으로 작업되고 있고 2.3을 쓰고 계시면 보시는 데 크게 무리가 없을 것 같습니다.
Rx쪽이 양이 많이 때문에 먼저 MVVM에 대해 말씀드리겠습니다.
오늘은 전체적으로 Why에 대한 이야기를 많이 해볼까 하는데요, 설계를 왜 굳이 해야 하냐는 얘기를 개발자들끼리 할 때가 있는데요, 물론 하면 좋지만 현실적인 문제들 때문이겠죠. 일정이 급하고, 할 것들은 많고… 그럼에도 불구하고 설계가 없는 개발은 찬성하지 않습니다. 유지보수 단계에 들어가게 되면 보통 새로운 프로젝트를 맡거나 새 기능을 개발하게 되는데요, 그 과정에서 기존에 만들어 두었던 구조를 완전히 수정하기엔 힘이 듭니다. 그래서 규모를 생각해 초기에 설계를 잘 잡아두는 것이 저희의 야근을 조금이라도 줄여준다고 생각을 합니다… ㅎㅎ
최근 같은 서비스의 안드로이드 프로젝트를 먼저 진행하며 이것들을 도입해서 설계를 해 봤는데요, 결과적으로 구조화는 잘 되었다고 생각하지만, 프로젝트 일정과 규모를 생각해 보았을 때 너무 오버엔지니어링을 하지 않았나 생각합니다.
그래서 iOS를 개발하게 되면서 안드로이드에서 느낀 점들과 타이트한 일정을 고려해 최소한도의 설계만 도입하려고 했죠. 그 결과 타이트한 일정에도 적절하게 유연하고, 나름 효율적이고, 카피&페이스트가 없고, 마감 막바지에 구조깨지지 않는 설계가 된 것 같습니다. Repository는 네트워크, DB등에서 데이터를 가져올 때, 로우 데이터를 가져오기 보다 해당 레이어의 데이터를 Mapper를 통해 매핑해 UI에서 사용하기 편한 방식으로 데이터를 알아서 처리해 주도록 구현하는 객체인데, 따로 페이지로 설명하지는 않도록 하겠습니다. 안드로이드에서 꽤 유용했던 패턴이라 필수라고 생각했고 지금도 여전히 그렇게 생각합니다.
그럼 왜 MVVM을 써야 할까요?
일반적인 MVC 패턴이죠.
MVVM의 다이어그램입니다. View와 ViewController를 통틀어 View라고 생각하면 되고, 가운데 View Model, 다음으로 Model이 있습니다. 뷰컨트롤러는 뷰모델을 가지고 있고, 뷰모델은 모델을 가지고 있죠.
이 중에서 MVVM 쪽을
이 중에서 MVVM 쪽을
그리고 이 부분을 MVVM 파트의 마지막으로 언급하고 싶은데요, iOS쪽 설계를 고민하며 MVVM을 포함해 이론적인 설명을 하는 블로그는 많고, 샘플 수준의 코드가 있는 프로젝트는 많았지만 앱을 개발할 때 어떤 구조로 코딩을 해야 할지 참고할 만한 프로젝트가 많지 않아 어려움을 겪었어요. 그래서 제가 진행했던 프로젝트는 어떤 모습인지 보여드리려고 합니다.
보여드리기 위해 프로젝트 이름과 불필요한 파일들을 레퍼런스에서 다 지운 상태입니다. 다음 슬라이드에서 파일들까지 보여 드릴 거에요.
이름들로는 바로 알기 어려울 순 있는데요, 네트워킹 부분 Repository 패턴을 위해 서버에서 받아오는 Raw 데이터 격인 Entity들, 그리고 이를 매핑하기 위한 매퍼들, 그리고 서버와 통신하는 부분의 구현체인 Service, 그리고 enum와 static 변수들이 있는 Types, Repository, 그리고 MVVM 파일들, Rx, Extension, Util들 이렇게 구분을 해 보았습니다. 저는 Xib와 스토리보드를 사용했기 때문에 관련 리소스들이 있구요.
두번째, Rx입니다. MVVM과 유사하게 코드 위주보단 제가 Rx를 이해하기 위해 진행되었던 사고의 흐름이 중요하다 생각해 그런 부분을 좀 더 다루려고 합니다.
먼저 역사를 언급하고 가야할 것 같은데요,
그럼 이제 Rx가 무엇인지 알아봐야겠죠?
앞쪽 역사에서 말씀드린대로 MS가 Rx를 만들었기에 MSDN에서 먼저 찾아보았습니다.
라이브러리인데, 비동기와 이벤트 기반의 프로그램을 구성할 수 있게 도와준다네요. Observable sequence와 LINQ-style 쿼리 오퍼레이터를 사용하구요.
저는 솔직히 처음에 봤을 때 무슨말인지 1도 모르겠더군요…
다만 세 단어만 체크하겠습니다. 비동기, 이벤트 기반, Observable.
ReactiveX 홈페이지의 설명을 보겠습니다. 조금 더 설명이 쉽네요. Observable 스트림을 사용해 비동기 프로그래밍을 하게 도와주는 API라고 하네요. Observable, 우선 관찰 가능한 스트림이라고 이해해 두시면 될 것 같습니다. 실제로 Observable이라는 객체가 있어요.
마찬가지로 홈페이지에 있는 설명인데요, 이걸 지금 모두 말씀드리지는 않겠지만, 위 아래에 선이 있는데, 이것이 Observable 스트림입니다. 스트림이 끝나지 않는 한 계속해서 반복적으로 입력으로 들어오는 데이터/이벤트들을 방출합니다. 이 스트림을 통해 비동기 프로그래밍을 할 수 있는 것이구요. 아래쪽을 설명이 참 간결하게 잘 되어 있다고 생각하는데, event 혹은 데이터 스트림을 만들고, query스러운 오퍼레이터를 통해 스트림을 구성하거나 변형하고, 마지막으로 Observable 스트림을 구독해서 얻은 결과물을 가지고 원하는 효과를 수행하라. 고 설명해볼 수 있을 것 같습니다.
이 라이브러리를 이해하기 위해 이 Reactive라는 단어의 의미를 알아두는 것이 도움이 된다고 생각해 적어봤는데요, 컴퓨터 사이언스에서 리액티브 프로그래밍이란 Object oriented programming처럼 하나의 패러다임을 의미합니다. 데이터 흐름과 변화의 전파에 근간을 둔 프로그래밍 패러다임이라네요… 무슨말인지 아시겠나요? 저는 마찬가지로 무슨말인지 모르겠더군요.
이해를 위해 앞 그림을 다시 보겠습니다. 데이터 흐름을 Rx에서는 Observable로 제공하고 있고, Observable이 계속 데이터를 위와 같이 방출합니다. 위 그림은 Observable에 debounce라는 오퍼레이터를 수행해 데이터에 변화를 줍니다. 그러면 결과 스트림에는 받은 데이터와 다른 변형된 데이터로 환산이 되는데요, 이렇게 변형된 데이터가 Subscribe하고 있는 곳으로 바로 전파됩니다. 이것을 변화의 전파라고 보시면 될 것 같습니다. 참고로 debounce는 이전의 아이템이 방출된 지 일정이상 시간이 지나야만 새 아이템을 스트림에서 방출하게 하는 오퍼레이터 입니다. 처음 아이템이야 이전이 없으니 바로 방출이 되고, 그 뒤 바로 붙어 있는 것들은 무시되고, 일정 시간이 지난 뒤 보라색 아이템은 방출이 되겠죠. 일종의 필터 기능이라고 보시면 될 것 같습니다.
Reactive 하다는 것이 결국 어떤 것인지 저는 이 예시가 참 좋다고 생각해 가져왔습니다. 스프레드시트에서 Reactive Programming을 사실상 할 수 있다고 생각하는데, 이런식으로 값을 변화에 따라 output인 그래프가 계속 변화함을 확인할 수 있습니다.
이제 Rx의 기반 개념들은 어느정도 말씀드렸다 생각하는데, 혹시 여기까지 어떠신지요? 많이 어려운 개념임에는 틀림이 없습니다… 이제 드디어 Rxswift를 볼 차례입니다. Rxswift의 경우는 완벽한 이해를 한다기보단, 이런식으로 사용할 수 있다 정도로 봐 주시면 될 것 같습니다.
github에 있는 RxSwift의 README에 있는 사용법 코드를 그대로 가져왔습니다. 문법 자체는 처음에 볼 때 난해할 수 있겠지만, 이런 식으로 사용할 수 있다는 것을 보면 좋을 것 같습니다. 설명을 해 보겠습니다.
searchBar의 입력값을 계속 데이터로 받아, 0.3초간 더 입력이 없다면, 입력값에 해당하는 검색 결과를 서버에서 가져와 Repository 리스트를 반환하도록 Observable을 구성합니다. 아까 보셨던 Flow들과, 오퍼레이터들과의 연계를 통해 원하는 결과물을 반환하는 Observable이죠.
아래쪽 로직은 뒤에서 한 번 더 언급을 하려고 하는데요, Data binding이라고 하는 것인데, Observable에서 받아오는 데이터인 Repository 리스트를 테이블뷰의 아이템들에 바인드해서, 각각의 아이템을 사용해 각 셀을 초기화하도록 구독해놓는 로직입니다.
설명을 보충하기 위해 제 프로젝트에 있는 코드를 약간 다듬어서 가지고 왔는데요, 로그인 버튼이 눌렸을 때의 동작입니다. 이메일, 비밀번호를 받는대요, 위쪽은 네트워크 상태 체크라 넘어가고, AccountService라는 곳에 로그인 시도를 하면 authUserEntity라는 것을 딱 한 번 받을 수 있는데요, 이를 Result라는 결과값으로 받아 로그인 성공 시 token을 통해 리스트를 fetch하도록 합니다. 그리고 리스트를 받은 이후엔 로딩 상태를 normal로 돌리구요. 로그인 실패 시에나, 에러가 생겼을 때 적절한 처리를 하도록 되어 있구요.
이 부분에는 앞 슬라이드의 아래쪽에 있던 Data binding 부분은 없고 단순 비동기 로직 처리만 있는데요, 그 부분은 MVVM과 연계해서 뒤에서 다시 다루려고 합니다.
이런 높은 학습곡선에도 불구하고 왜 써야 할까요?
앞에서 보셨듯이 상당히 복잡한 로직들인데, 비교적 간결하게 표현이 가능합니다. callback이 애초에 하나도 없었죠.
그리고 이번 Android/iOS 프로젝트를 진행하며 Android의 AsyncTask, iOS의 GCD, NSOperation, Thread 등을 단 하나도 사용하지 않았습니다.
그리고 메이저 비동기 오픈소스들이 Rx를 많이 지원하는 추세입니다. Android도 마찬가지이구요.
그래서 Rx에 적응하는데 일정 기간이 필요했지만, 이제 앞으로도 계속 비동기 로직은 모두 Rx로 처리할 생각입니다. 마치 git을 한 번 쓰고 난 이후엔 절대 다시 돌아갈 수 없었던 것과 비슷한 느낌인 것 같습니다.
자… 마지막 주제인데요, 앞서 말씀드린 두 요소를 사용해 Data binding을 어떻게 사용했는지 말씀드리겠습니다.
앞 예제를 다시 보죠. 위쪽 코드는 Observable을 생성하는 코드입니다. 텍스트 스트링이 계속 인풋으로 들어오는 Observable에 여러 처리를 통해 Repository 리스트를 방출하는 Observable로 변환하는 로직인데요,
아래 로직이 Data binding 로직입니다. 다시 살펴보면, 이 Observable에서 방출되는 데이터, 측 Repository 리스트라는 데이터를 테이블뷰 items에 바인딩을 하는데, 그 결과 이 리스트에서 하나하나 꺼내진 Repository가 각각의 셀로 변환이 되고, 변환이 완료된 이후 자동으로 테이블뷰에 표시가 되는 로직이죠.
아이디, 비밀번호 유효성을 검증하는 로직을 만들어, 둘 다 유효한 값일 때 아래쪽 로그인 버튼을 활성화되도록 만들고 싶은 상황이 있다고 가정해 보겠습니다.
다음과 같은 방법으로 Driver를 통해 구성할 수 있습니다. combinLatest라는 오퍼레이터가 보이는데요, 이게 무엇인지 먼저 설명을 하고 다시 돌아오겠습니다.
각 텍스트 필드에 빈 스트링이 아닌 값들 들어올 때 유효성 검증 메서드로 체크를 해 Bool 변수를 반환하는 Observable을 각각 만듭니다. 이후 combineLatest를 통해 두 Observable을 결합해 둘 다 true일 경우에만 true를 반환하는 Observable을 만듭니다. 그리고 아래쪽 distinctUntilChanged()는 새 값이 이전 값과 같을 때 무시하고 바뀔 때만 방출하는 필터링 오퍼레이터입니다. 유효성이 바뀔 때에만 isEnabled를 처리해 주고 싶어서 해당 오퍼레이터를 넣었어요.
마지막으로 데이터 바인딩인데요, 만든 Driver를 signInButton의 rx.isEnabled에 drive를 해 줍니다. 그러면 해당 Driver의 Bool값을 이 값에 바인딩해 값이 동기화가 되도록 해 줍니다.
마지막으로 signInButton 클래스인데요, isEnabled의 값이 변화할 때 마다 버튼의 배경 색과 타이틀 색을 바꾸어 주도록 구현했습니다. 그래서 Data binding을 통해 입력값의 변화에 따라 UI가 같이 변화할 수 있도록 구현이 되었습니다.