밥과 함께 RxSwift 시작하기

왜 RxSwift 또는 반응형 프로그래밍을 사용하나요?

RxSwift를 처음 본 아이의 표정

: 안녕하세요. 이 글은 며칠 전에 제가 영문으로 글을 쓴 이후 요즘 뜨고 계신 필권님에게 의뢰 하여 한국어로 멋지게 번역해주셨습니다. 다들 아시겠지만, 영어로 쓰다 보면, “you”, “~해라”라는 표현을 자연스럽게 사용 수 있습니다. 하지만, 한국 문화에 맞지 않음을 이해 부탁드립니다. 그러면 이제 시작해볼까요?


글을 쓰게 된 동기

제가 RxSwift 를 공부하게 될 때 좌절감을 느꼈습니다. “이거 저거 하면 이건 이렇게 그냥 작동해”라는 글 들이 너무 많더군요. 구글의 검색 결과 첫 10페이지를 읽은 후 가장 중요한 이 나오지 않았던 기억이 납니다.

블로그를 운영하면서 1000명이 넘는 iOS 개발자와 학생에게 설문조사를 해보았습니다. 물론 RxSwift도 종종 언급되었습니다. 그러나 대부분은 이 프레임워크를 실제로 사용하기에는 가파른 학습 곡선 때문에 힘들다고 하시더라구요.

이제 그 이유를 알겠습니다. RxSwift 를 배우기에 앞서 필요한 개념을 비유와 이야기를 통해 설명할 수 있는 사람이 많지 않았던 겁니다. 그래서 저는 다짐합니다. 저의 손을 더렵힘을 통해(get my hands dirty) 제가 사랑하는 커뮤니티에 기여하기로 결정했습니다.

사고방식 배우기

저는 그냥 따라하기(just by doing it)로 시작하는 스타일은 아닙니다. 배움에 있어서 저의 접근 방식은 어떻게(how)보단 왜(why)의 근본적인 이유를 찾는 것이죠. 많은 분들은 제품/앱을 만드는데에 초점을 맞추는 경향이 있습니다. 마치 시험을 보기 위해 수학 공식을 외우는 것처럼요. 초반에 더 많은 투자와 노력이 필요하지만 근복적인 이해를 통해 불안감 대신 자신감과 확신을 가지고 코드를 작성 하실 수 있을 것입니다.

네 “그냥 해보기”로 배울 수 있습니다. 계란을 던져서 물리적으론 돌을 꺨 수 없다는 것을 배울 수 있습니다. 다른 한편으론, 유튜브 비디오로 계란으로 바위치는 영상을 볼 수도 있습니다.한편으로 공기역학과 각운동량에 대한 책을 읽는 것 만으로 자전거 타는 법을 배울 수 없습니다.

둘 사이의 균형이 있어야 합니다. 제 학습에 있어서, 저는 시작하기 전에 저의 많은 시간을 이론과 만들어진 예제들을 둘러보는데에 시간을 많이 투자합니다. 패러다임이나 아키텍쳐를 6살 어린 아이에게 설명할 수 있기 전까지는 Xcode열어보지 않습니다.

왜라는 질문하기

이 글을 통해 왜 RxSwift를 쓰는지에 대해 설명드리고 싶습니다.

Viktor Frankl의 “Man’s search for meaning”에는 제가 좋아하는 문구가 있습니다.

‘He who has a why to live for can bear almost any how — 사는 “이유 “에 대해 아는사람은 어떤 “어떻게”도 다 포용할 수 있다.

저자는 나치의 강제 수용소에서 살아남을 수 있었습니다. 의사로서, 그는 살아야 할 이유가 있었습니다. 먼저, 살아서 가족을 보기 위해서. 두번째, 지역 사회를 도우기 위해서. 학습에 있어서도 같은 사고방식이 적용됨을 깨닳았습니다. 만약 `RxSwift`를 “왜” 쓰는지 알게 된다면, 결국 실제 상황에서 이 개념을 예제와 상상력의 균형을 통해 적용하는 방법을 발견하게 될 것입니다.

독자

이 글은 숙련된 개발자들을 위해 작성되었습니다. 만약 독자분들께서OOP, POP, ARC, 델리게이트, 콜백대한 이해가 없이 함수형/반응형 프로그래밍을 배우려 한다면, 나머지를 이해할 수 없을 것입니다. 만약 모든 것을 한 번에 배우시고 싶으시다면. Bob과 함께 Swift 4 배우기(중급자용)…

: 아쉽게도 아직 한국어 버전이 없습니다. 하지만 영어로 Swift를 깊게 배우고 싶은 분들은 이 강의 추천드립니다.

RxSwift의 정의

많은 튜토리얼들이 RxSwift를 개발자들이 반응형과 함수형 패러다임으로 코딩하게 해주는 도구로 설명하면서 시작합니다. 하지만, 대부분의 그 글들은 함수형(functional)반응형(reactive)라는 용어의 의미와 왜 우리가 이 철학을 채택한 이유를 설명하지 못합니다. 그들은 기존의 것에 비해 이득을 설명하지 못합니다. 그들은 단지 “예제를 보여주기” 바쁩니다. — “왜” 보다는 “어떻게”죠.

메모” RxSwift는 스위프트로 만들어진 라이브러리입니다. 다른 언어로는 RxJS, RxKotlin, RxScala, Rx.NET이 있습니다. 그들은 모두 ReactiveX가 정한 패러다임을 따르고 있습니다.

RxSwift에 들어가기 앞서, 반응형함수형에 대해 알아봅시다.

함수형 프로그래밍이 무엇인지 알아보기

RxSwift의 첫 번째 원칙은 함수 기반 코딩 스타일입니다. 저는 이미 밥과 함께 스위프트 함수형 프로그래밍 시작하기에 대한 글을 썼습니다. 그래서 다시 한 번 몇 개의 포인트를 짚고 넘어가겠습니다.

정의: 함수형 프로그래밍은 동료와 소통하고 문제를 해결하기 위해 함수를 쓰는 것 이상입니다.

함수형 프로그래밍의 진가를 알아보기 위해, 문제 해결의 “일반적인” 방법부터 보겠습니다.

명령형(Imperative) 프로그래밍

우리가 어떤 배열을 가지고 있다고 해봅시다.

let 나의성적 = [“A”, “A”, “A”, “A”, “B”, “D”]

우리는 “A”만 받기를 원할 것입니다. 명령형 프로그래밍을 시작해봅시다.

var 행복한성적: [String] = []
for grade in recentGrade {
if grade == “A” {
행복한성적.append(grade)
} else {
print(“엄마는 행복하지 않았다.”)
}
}
print(행복한성적) // [“A”, “A”, “A”, “A”]```

선언형(Declarative) 프로그래밍

위의 8줄의 구현은 끔찍합니다. 이제 함수를 좀 더 함수형스럽게 사용해 봅시다.

stringFilter(grade: 나의성적, returnBool: { $0 == “A” })
// [“A”, “A”, “A”, “A”]
메모: 만약 명령형과 선언형의 차이를 이해하는데 고생을 하고 계시다면, 위에 첨부된 글/강의를 보고 돌아오시는 것도 괜찮습니다. 다시 한 번 말씀드리면, 클로저와 완료 핸들러에 대해 강한 이해를 가지고 있어야 합니다.

함수형 프로그래밍은 다음과 같은 것을 제공합니다:

  1. 상태(State) 관리 필요없음
  2. $0의 사용을 통한 간결한 의사소통
  3. 유닛 테스트 가능성
  4. 짧음

저는 이 전의 글에서 이미 각각의 이점을 설명했습니다.

반응형 프로그래밍은 무엇일까요?

RxSwift가 받아들인 두번째 패러다임은 반응형 프로그래밍입니다. 반응형 프로그래밍의 진가를 알아보기 위해, “비 반응형” 코드를 한 번 봅시다.

비 반응형 프로그래밍

숫자 두 개를 더해봅시다.

var a = 1
var b = 3
a + b // 4

결과는 4입니다. 좋고 쉽죠. 하지만, a를 다른 숫자로 바꾸고 싶다면 어떡할까요?

var a = 1
var b = 3
a + b // 처음(4) -> 마지막(4)
a = 3

지금은 다른 점이 없습니다. a 에 할당된 값을 1에서 3으로 변경하더라도, a + b는 여전히 4를 반환합니다. 이것은 스위프트에서 “보통의” 코드입니다. a + b 는 아무일도 신경쓰지 않습니다. 그것은 정해진 시간에 그것의 비즈니스(mind its business)만 신경씁니다.

반응형 프로그래밍

이제 반응형 프로그래밍이 무엇을 의미하는지 알아봅시다. 동일한 예제를 사용해보겠습니다. 하지만, 우리는 그 숫자들을 이미 만들어져있는 클래스인 Reactive로 감싸보겠습니다.

var a = Reactive(1)
var b = Reactive(3)
a.value + b.value // 4`
메모: 위의 예에서 보았듯이, 저는 Reactive 라는 가짜 클래스를 사용했습니다. 왜냐하면 String과 같은 기본 값은 스위프트에서 비 반응형이기 때문입니다.

이제 a의 값을 3으로 변경해보겠습니다.

var a = Reactive(1)
var b = Reactive(3)
a.value + b.value // 처음(4) -> 마지막(6)
a.value = 3

이제, 차이점이 생겼습니다. a.value + b.value는 여전히 ab의 값에 신경을 쓰고 있습니다. 이것이 반응형 프로그래밍입니다. 반응형 프로그래밍은 변화를 기다리고 변화가 있으면 나타내고/반응하고/보여주고/표현하고/구독하고/관찰합니다. 이것은 NSNotification과 비슷합니다.

이것이 반응형 프로그래밍의 근본적인 기반입니다.

상상

이 반응형 패러다임이 실제 iOS 프로그래밍에 있어서 얼마나 강력할지 상상하는 시간을 가져봅시다.

반응성의 특성으로 인해, RxSwift는 비동기 코드를 핸들링하는데 왕으로서, 변경을 할 때마다 무언가가 일어났음을 알려줄 것입니다.

예를 들어, UIViewUIDevice의 속성을 반응형으로 만들면, 쉽게 변화를 느낄 수 있고 완료 핸들러/콜백을 통해 다른 라인의 코드를 실행할 수 있습니다. 이제 기기 방향 속성을 반응형으로 만들어봅시다.

let 기기방향 = UIDevice.current
let 반응형방향 = Reactive(기기방향)

기기방향을 “반응형”으로 만들었습니다. 사용자가 기기를 돌리면, 아래의 수도 코드를 통해 변화를 “관찰”할 수 있습니다.

반응형방향.observe { 현재방향 in
switch 현재방향 {
case .오른쪽가로모드: print(“가로!”)
case .위아래바뀐세로모드: print(“이상한 세로!”)
case .왼쪽가로모드: print(“가로!”)
case .세로모드 { print(“세로!”) }
}
}

이제 유저가 4번 90도씩 기울여 봅시다.

// “가로!"
// “이상한 세로!”
// “가로!”
// “세로!”

꽤 쉽죠? 이것이 바로 반응형 프로그래밍의 근본적인 기반입니다.

함수형 프로그래밍

그러면, 함수형 프로그래밍은 어떻게 작동할까요? 반응형 객체에서 받은 이벤트를 다루기위해 함수를 적용할 수 있습니다.

기기가 세로모드일 때만 `출력`하길 원한다고 가정해 봅시다.

반응형방향.observe { 현재방향 in
.filter { 현재방향 == .세로모드 }
.observe { 현재방향 in
switch 현재방향 {
case .오른쪽가로모드: print(“가로!”)
case .위아래바뀐세로모드: print(“이상한 세로!”)
case .왼쪽가로모드: print(“가로!”)
case .세로모드 { print(“세로!”) }
}
}

또는 스위프트의 클로저를 이미 마스터했다면, 아래와 같이 구현할 수 도 있습니다.

.filter { $0 == .세로모드 } // 아니라면 돌아갑니다
.observe {
switch $0 {
case .오른쪽가로모드: print(“가로!”)
case .위아래바뀐세로모드: print(“이상한 세로!”)
case .왼쪽가로모드: print(“가로!”)
case .세로모드 { print(“세로!”) }
}
}

폰을 시계방향으로 90도씩 돌려봅시다.

// 출력되지않음
// 출력되지않음
// 출력되지않음
// “세로!”

다시 한 번 말씀드리면, filter 함수는 모든 데이터/이벤트에 적용됩니다. 함수형/반응형 패러다임은 적은 라인의 코드를 작성하게 도와주고 가독성을 높여줍니다. 이전의 조건문이 참임을 보기 위해서, 비동기 코드를 처리하기 위한 몇 가지 다른 예제를 살펴보겠습니다.

만약 스위프트의 비동기 코드에 익숙하지 않다면, iOS의 델리게이트와 콜백을 읽으시면 됩니다. 다시 한 번 말씀드리면, 비동기는 이벤트/실행의 비정기적인 흐름으로 설명할 수 있습니다.

델리게이트랑 비교하기

더 이상 거북한 UIViewController가 수많은 프로토콜에 부딪히는 일은 없을것입니다.

extension 나의뷰컨트롤러: UIViewController, GoogleMapDelegate, GoogleMapDataSource, UIDeviceOrientationDelegate, UITableViewDelegate, AnotherDelegate {
// 요구되는 수많은 메소드들
// 다루기 힘듭니다
}

더 이상 objc를 “옵셔널을 요구하는 함수를 위해 쓸 필요가 없습니다.

@objc protocol UIDeviceOrientationDelegate {
@objc optional func didChangeDeviceOrientation()
}

KVO와 비교하기

훌륭하고 오래된 Objective-C 개발자들은 특정 객체의 변화를 추적하기 위해 KVO를 사용합니다.

-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context

위의 코드를 이해할 필요는 없습니다. 그저 keypath를 선언하는 속성을 관찰하면 됩니다. 걱정하지마세요. 스위프트 개발자로서, 이 API를 쓸 일은 거의 없을 것입니다.

NSNotification과 비교하기

RxSwift에선 구독하기 위해서 subscribe 메소드 하나를 호출하면 되는 반면에, 관찰자(observer)를 구독하려면, 4개의 파라미터가 필요합니다. 함수형 패러다임은 적용하지 않았습니다.

NotificationCenter.default.addObserver(self, selector: #selector(알림이오면실행할함수), name: NSNotification.Name(rawValue: 나의노티피케이션키), object: nil)
}

속성 관찰자(Property Observer)와 비교하기

또한 속성 관찰자를 사용할 것입니다.

var 내기기현재방향 = UIDevice().current {
willSet(새로운방향) {
switch 새로운방향 {
case .오른쪽가로모드: print(“가로!”)
case .위아래바뀐세로모드: print(“이상한 세로!”)
case .왼쪽가로모드: print(“가로!”)
case .세로모드 { print(“세로!”) }
}
}

속성 관찰자는 코드가 변화하는 것을 다루는 강력한 방법을 제공합니다. 그렇지만 단점을 가지고 있습니다. 먼저, 만약 두 개 이상의 객체가 새로운 데이터에 신경쓰고 있다면 아래와 같이 만들어야 할 것입니다.

var 내기기현재방향 = UIDevice().current {
willSet(새로운방향) {
함수1(새로운방향)
함수2(새로운방향)
함수3(새로운방향)
}
}

속성 관찰자 영역은 거북해집니다. `RxSwift`를 쓴다면 나눌 수 있습니다.

내기기현재방향 = UIDevice().current
let 반응형방향 = Reactive(내기기현재방향)

함수를 불러 볼까요?

// 함수1
func 함수1() {
반응형방향.observe { print($0) }
}
// 함수2
func 함수2() {
반응형방향.observe { print($0) }
}
// 함수3
func 함수3() {
반응형방향.observe { print($0) }
}

이제 코드가 더욱 모듈화됐습니다.

마지막으로

이 글의 목적은 RxSwift로 앱을 만들기 시작하는 것이 아닙니다. 오히려, RxSwift의 기본 구성 요소를 이해하는 것이 목적이었습니다. 반응형 프로그래밍은 완전히 새로운 구기 종목(a whole new ball game)입니다. 앞으로는 RxSwift를 통해 계속 상세하게 진행할 계획입니다.


밥: 안녕하세요! 끝까지 읽어주셔서 감사드립니다. 필권님께서 공 들으셔서 번역해주신 것 에 대해 큰 감사를 표현하고 싶습니다. 그리고 미래에도 계속 Swift에 관한 (코드까지) 번역된 글 그리고 다양한 컨텐츠: Test Driven Development, RxSwift에 대해서 컨텐츠를 배포할 예정입니다. 관심 있으신 분들은 여기에 메일을 적어주시면 한국어로 업데이트 직접 드리겠습니다. 이번 한 주도 승리하시고 휴가 가족들과 즐겁게 보내세요!— 밥 드림