이 글에서는 iOS에서의 MVVM 디자인 패턴과 RxSwift를 소개합니다. 또한, 2개의 파트로 구성되어 있으며 이번 파트에서는 RxSwift의 기초와 디자인 패턴에 대해 간략히 설명하고, 이어지는 파트2에서 MVVM 패턴과 RxSwift를 적용한 예제 프로젝트를 진행하려 합니다.

우선 이 글은 Mohammad Zakizadeh의 글을 번역한 것임을 알리고(Thanks👏), 중간중간 부족한 내용은 보충하기도 했습니다. 그럼 바로 시작하겠습니다.

디자인 패턴

먼저 왜 우리가 디자인 패턴을 사용하는지에 대해서부터 이야기하는 것이 좋을 것 같습니다. 간단히 말해 코드가 스파게티🍝가 되지 않기 위해서 입니다만, 이게 유일한 이유는 아닙니다. 다른 이유 중 하나는 테스트 용이성일 것 입니다. 우리가 흔히 아는 디자인 패턴에는 MVC, MVP, MVVM, VIPER 등이 있으며 아래의 사진은 NSLondon에서 각 디자인 패턴을 비교한 사진입니다.

모든 디자인 패턴에는 각각의 장단점이 있고, 이를 통해 우리의 코드를 더 읽기 쉽거나 단순하게 하여 발전시킵니다. 이 글에서는 MVVM에 초점을 맞추고 있으며, 이어지는 파트2를 통해 MVVM의 장단점을 스스로 깨달았으면 좋겠습니다.

그럼 간단히 MVC 패턴부터 살펴보도록 하겠습니다.

MVC

이 글을 읽고있는 분들은(애플이 권장하는 방식이기 때문에) 아마도 MVC 패턴에 많이 익숙할 것 입니다. 이 패턴은 Model, View, Controller로 구성되어 있고 Controller가 Model과 View를 연결하는 책임을 가지고 있습니다. 이론적으로는 View와 Controller가 다르지만, 불행하게도 iOS에서는 대부분 View와 Controller가 하나가 됩니다. 물론, 작은 프로젝트에서는 비교적 잘 정돈된 편이지만 규모가 커지게 되면서 Controller가 거의 모든 책임을 지는 일명 Massive View Controller가 되고 맙니다. 물론 당신이 정확한 방법으로 코드를 잘 배분하면 해결될 문제이긴 합니다만…

MVVM

MVVM은 Model, View, View Model의 약자로 View와 애니메이션은 View가, 비즈니스 로직과 api 호출은 View Model이 담당합니다. View Model은 View에서 필요한 데이터를 Model과 상호작용하여 가지고 있지만, 직접 건네주는 것이 아니라 View에서 바인딩을 통해 View Model에게서 필요한 데이터를 가져가야 합니다. 또한, View Model은 View에 대해 아무것도 몰라야 하기 때문에 import UIKit이 코드상에 있어서는 안됩니다.

RxSwift

위에서 말한 View와 View Model을 바인딩하기 위한 효과적인 도구로 RxSwift가 있습니다. 물론 델리게이트나 KVO 혹은 클로져를 사용할 수 있지만, RxSwift는 한 번 배우면 다른 언어의 Rx문법 습득이 쉽다는 장점이 있습니다. 이제 RxSwift의 기본에 대해 설명해보겠습니다.

Reactive Programming

RxSwift는 Reactive Programming에 기반을 두고 있습니다. Reactive Programming이 무슨 뜻일까요? 사전적 정의를 늘어놔도 당장은 이해하기 힘드니 바로 코드를 봅시다.

var a = 3
var b = 7
var c = a + b
a = 1
print(c) // 10

우리는 c가 10을 출력하는 것에 익숙합니다. c가 만들어질때 ab의 값이 각각 3과 7이었기 때문입니다. 하지만 Reactive Programming 하에서는 결과가 다릅니다.

var a = 3
var b = 7
var c = a + b
a = 1
print(c) // 8

무슨 차이가 있는걸까요? Reactive, 반응형, 즉 변화에 반응하여 자동으로 변경된다는 것이 Reactive Programming의 핵심입니다. 그렇기 때문에 변화에 대해 우리가 일일히 반응하지 않아도 됩니다. 이보다 쉬운 예제가 있을까요? 그러면 지금부터 RxSwift의 기초를 알아보겠습니다.

RxSwift 세계에서는(Rx는 모두 동일) 모든 것이 스트림이며 UI 이벤트나 네트워크 요청도 역시 스트림입니다. 스트림이란 것이 처음엔 이해하기 어려울 수 있지만, 버튼을 누르면 특정 문자열을 출력하는 함수가 있다고 가정하면 버튼을 누른 순간부터 출력을 완료하는 과정 자체가 하나의 스트림이라고 이해하시면 됩니다.

본격적으로 RxSwift에 대해 알아보기전에 RxSwift에서 쓰이는 개념인 Observable과 Subscriber에 대해 실생활의 예를 통해 알아보겠습니다.

우리는 항상 휴대폰을 예의주시하고 있습니다. 전화가 오는지, 카톡이 오는지 등 항상 신경쓰고 있습니다. 전화가 왔을때 어떤 경우에는 무시하기도 하고 어떤 경우에는 전화를 받습니다. 이런 일련의 과정이 RxSwift의 핵심입니다. 우리가 항상 휴대폰을 예의주시하고 있는 것, 이것을 RxSwift에서는 Subscribe(구독하다) 라고 부릅니다. 우리가 바로 구독자(Subscriber)인 셈이죠. 반면 휴대폰은 Observable(관찰가능한) 이라고 불립니다. 우리는 필요에 따라 관찰가능한 휴대폰을 구독하고 있다가 휴대폰이 방출하는 이벤트에 반응하는 것 입니다. 이게 이해가 됐다면 RxSwift의 핵심을 이해했다고 볼 수 있습니다. 👏

자 이제 RxSwift의 세계로 빠져봅시다.

Observable과 Observers(Subscribers)

Rx의 세계는 Observable와 Observers(혹은 Subscribers)가 존재합니다. ObservableType protocol을 준수한다면 어떤 것도 Obseravable이 될 수 있습니다. 바로 Observable을 정의해보겠습니다.

let observableString = Observable.just("Hello, RxSwift")
let observableInt = Observable.of(0, 1, 2)
let observableDict = Observable.from([1: "Hello", 2: "Swift"])

우리는 세 종류의 Observable을 정의했습니다. 각각에 사용된 just(_:), of(_:), from(_:) 등은 Observable을 생성하는 연산자 입니다. 참고로 RxSwift에서는(모든 Rx 동일) 함수를 연산자라고 칭합니다. 지금은 일단 이 연산자가 뭔지 정확히 몰라도 됩니다. 그저 ObserverType protocol을 채택하면 어떤 것이든 Observable이 될 수 있다는 것을 보여주기 위한 예제입니다. 그러니 이게 Observable을 생성하는 연산자라는 것만 알아두세요.

자 이제 Observable이 만들어졌으니 구독을 시작해볼까요?

let observableString = Observable.just("Hello, RxSwift")
observableString.subscribe { print($0) }
// next(Hello, RxSwift)
// completed

이 결과를 보면 여러가지 의문이 생길 수 있습니다. 가령 왜 그냥 Hello, RxSwift만 출력되지 않고 next가 붙는지, completed는 무엇인지 등…이에 대해서는 이제부터 알아볼 것 입니다.

아까 Rx 세계에서는 모든 것이 스트림이라고 했었죠? 아래의 그림을 보면서 이야기하는게 빠를 것 같습니다.

위 그림에서는 3개의 Observable을 볼 수 있는데 첫 번째는 Int형이고 1부터 6까지 각 Int형의 데이터가 6번 방출되고 종료되었고, 두 번째는 String형의 값인 a~f까지 방출하다가 에러가 났습니다. 마지막 그림은 세번의 탭 이벤트가 발생했습니다. 이 그림은 일명 Marble Diagram이라고 불리며 우선은 어떻게 읽는지 알아보도록 하겠습니다.

먼저 가로선은 시간의 흐름입니다. 이것이 아까 설명했던 스트림이라고 이해하시면 됩니다. 즉 이 가로선이 하나의 스트림입니다. 시간 순서대로 탭 이벤트가 발생했거나 값을 방출하는 이벤트를 위 그림처럼 표현할 수 있습니다. 가로선이 | 를 만나게 된다면 하나의 스트림이 종료된 것입니다. 또 error를 나타내는 X를 만나면 마찬가지로 스트림이 종료됩니다. 반면 마지막 탭 이벤트의 경우 completed나 error 없이 계속되는 상태입니다. Rx의 연산자와 Marble Diagram에 대해 더 많은 정보는 RxMarbles에서 얻을 수 있습니다.

자 이제 간단히 Marble Diagram에 대해 알아봤으니 next, completed등에 대해서 다시 알아봅시다. Rx 하에서는 스트림이 nextcompleted, error 3가지 종류의 이벤트로 구성되어 있습니다.

Observable이 구독자에게 방출할 값이 있다면 next 이벤트를 통해 방출합니다. 이렇게 방출하다가 더이상 방출할 값이 없다면 마지막으로 completed 이벤트를 방출하고 종료됩니다. 또 만약 중간에 error가 발생한다면 error 이벤트를 방출하고 종료됩니다. 다시 한 번 말하지만, 이 과정을 스트림이라 볼 수 있습니다.

그럼 첫번째 그림을 코드로 표현해봅시다.

var observableInt = Observable.of(1, 2, 3, 4, 5, 6)
observableInt.subscribe { print($0) }
// next(1)
// next(2)
// next(3)
// next(4)
// next(5)
// next(6)
// completed

어떤가요? 이제 조금 감이 오시나요? of(_:)연산자가 뭔지 지금부터 정확히 이해하지 않으셔도 됩니다. 아까 말했듯 이게 Observable을 생성하는 연산자라는 것이 지금은 중요합니다.

자 그럼 다시 Rx의 세계에 빠져봅시다. 만약 우리가 구독을 취소하고 싶다면 어떻게 해야할까요? 우리는 Observable을 구독할 수 있듯 취소할 수도 있습니다. 이것을 우리는 Dispose라고 부르며 Rx에서는 DisposeBag에 담아 구독을 취소하는 과정을 거치게 됩니다. (이 과정을 거치는 이유는 순환 참조로 인한 Memory Leak을 피하기 위해서 입니다.)

그렇다면 구독을 취소하는 과정을 알아보겠습니다.

let disposeBag = DisposeBag()

var observableInt = Observable.of(1, 2, 3, 4, 5, 6)
observableInt
    .subscribe { print($0) }
	.disposed(by: disposeBag)

이렇게 되면 ViewController가 메모리에서 해제될때 disposeBag에 담긴 모두가 메모리에서 같이 해제됩니다. 만약 중간에 해제하길 원한다면 disposeBag을 초기화시켜주면 됩니다.

disposeBag = DisposeBag()

자 이제 RxSwift에 필요한 기초는 거의 배웠다고 할 수 있습니다. 각각의 연산자는 하나의 글에 담기에는 너무 방대하기 때문에 스스로 공부하길 권장하며, 아주 간단한 연산자 하나만 소개하고 이 글을 마치도록 하겠습니다.

Int형을 방출하는 Observable을 구독하고 있다가 2의 배수인 경우에만 10을 곱해서 출력하는 RxSwift 코드를 만들어보겠습니다.

let disposeBag = DisposeBag()

var observableInt = Observable.of(1, 2, 3, 4, 5, 6, 7, 8)
observableInt
    .filter { $0.isMultiple(of: 2) } // 여기에서 2의 배수만 걸러집니다.
    .map { $0 * 10 } // 여기에서 10이 곱해집니다.
    .subscribe { print($0) } // 여기에서 방출되는 값들이 출력됩니다.
    .disposed(by: disposeBag) // 모든 값이 방출되면 구독을 취소합니다.
// next(20)
// next(40)
// next(60)
// next(80)
// completed

네 맞습니다. filtermap 모두 Swift의 고차함수 입니다. 이미 이런 종류의 고차함수를 자주 써봤다면 특별히 앞으로 RxSwift의 코드를 이해하는 것에 어려움이 없으리라 생각됩니다.

여기까지 MVVM과 RxSwift 기초에 대해서 알아봤습니다. 사실 지금까지 알아본 RxSwift는 빙산의 일각이라고 볼 수 있습니다. RxSwift를 제대로 사용하기 위해서는 수많은 벽을 넘어야 합니다. MVVM 또한 Coordinator와 결합된 MVVM-C 패턴까지 공부하려면 많은 시간을 들여야만 합니다. 그러나 높은 러닝커브에 미리 두려움을 느껴 주저하는 저같은 개발자가 있을까봐 최대한 쉬운글을 골라 번역하고 보충 내용을 적거나 내용을 수정을 했습니다.

번역되지 않은 글은 여기에서 확인할 수 있습니다. 그럼 곧 파트2로 다시 만나요.👋