- 본 글은 RxSwift의 개념을 예제를 통해서 설명해주는 곰튀김님의 강의를 보고 내용을 정리한 글이다.
강의에서 구두로 설명한 개념 중에서 정리가 필요한 중요한 부분들을 나름대로 정리하여 글을 써보았다. - 곰튀김님 인강 링크
1. RxSwift를 사용하는 목적
보통 Rx를 쓰는 이유로 콜백 지옥 극복 및 데이터/UI 작업 코드를 경량화 하여 사용하는 2가지 장점을 언급한다.
이에 따라 RxSwift의 2가지 장점이 어떻게 발현이 되는지 아래와 같이 정리해보았다.
2. 콜백지옥
보통 Rx를 써야하는 근거 중 하나를 말할 때 콜백 지옥을 많이 말하는데 그런 경우는 아래와 같은 상황을 말한다.아래 코드를 보면 비동기 처리를 계속해서 연속적으로 사용할 경우 콜백 지옥에 빠지게 된다. 이럴 경우 코드의 가독성이 떨어지는 문제가 발생한다.이런 코드들을 RxSwift를 사용하여 간결하게 표현할 수 있다. → 예시는 뒤에 나온다.
3. 데이터/UI 처리 작업 모듈화 및 RxSwift의 원리
1) 개선 전 코드
개선전 코드를 보면 코드의 목적을 알 수 있는데 그 목적은
- 비동기적으로 데이터를 받아오고
- 받아온 데이터를 메인스레드에서 그리는 목적을 가지고 있다.
하지만 위 코드를 보면 데이터를 받아오는 비동기처리 및 UI를 그리는 직렬작업 처리를 일일히 구현해주는데 이것을 아래와 같이 모듈화를 할 경우 간편하게 코드를 구현할 수 있다. → 이것이 RxSwift를 사용하는 목적이다.
2) 개선 후 코드
위 코드를 해석하는게 상당히 난해하다. 비동기적 처리를 여러개를 엮다보니 코드를 해석하는 것이 상당히 어렵다. 어떻게 보면 이런 어려움이 있어 @escaping 클로저를 통한 비동기 처리를 RxSwift로 대체하려고 한 것이라고 생각된다.
위 코드를 해석해 보면
- 나중에생기는데이터 클래스를 downloadJson 함수를 통해서 생성해준다.
- 그리고 나중에생기는데이터 클래스 내부를 보면 초기화때 @escaping 클로저를 사용해서 초기화를 하도록 되어있다. 이에 따라서 나중에생기는데이터 클래스의 후행클로저에 의하여 초기화가 된다.
즉, 나중에 생기는 데이터 클래스 내부에 있는 task가 아래에 있는 후행클로저가 된다는 것이다.
- 위와 같이 될 경우 task 는 아래와 같은 코드가 된다.
위 코드를 해석해보면 task 함수는 f라는 클로저를 변수로 전달받게 되는데 해당 클로저는 @escaping 성향을 가지고 있고, 이 코드는 아래 로직을 통해 생성된 json 데이트를 변수로 전달받게 된다. 이에 따라 task 함수의 생명주기에서 벗어나 비동기적으로 json 테스크를 처리하게 될 것이다.
- 그후에 아래와 같은 코드가 실행되는데
위의 코드를 보면 나중에오면 이라는 함수는 task 함수에 f 라는 이스케이핑 클로저를 주입하고 있다. 이말은 즉, task 내부 변수 f 에 나중에오면 함수의 후행 클로저가 주입된다는 것을 의미한다. 해당 내용을 아래 코드와 같이 표현할 수 있을 것이다.
이 코드에서 나중에오면 이라는 함수가 어떤 것인지 찾아보면 아래와 같다. - 위와 같이 후행클로저의 변수를 복잡하게 전달해줌에 따라 비동기 처리를 위한 @escaping 클로저 사용을 없이 “나중에오면” 이라는 함수의 return 값으로 비동기 처리를 할 수 있게 되었다. 하지만 상당히 복잡한 내부 로직이 구현되어 있어 그 내부를 파헤치기는 것이 쉽지 않았고 이는 즉 RxSwift 설계가 상당히 복잡하게 구현되어 있을 거라는 것을 의미하기도 한다.
- 그리고 이것을 RxSwift에 대입해보면
1) 나중에오는데이터 == Observable
2) 나중에오면 == subscribe
가 된다. - 위와 같은 과정을 통해서 RxSwift의 의미를 알 수 있는데 그 의미는 아래와 같다.
@escaping 클로저를 통해서 비동기처리하는 것을 return 값으로 처리가 가능하도록 래핑을 하였고, 그결과 코드를 간결화할 수 있었다.
좀 더 구체적으로 얘기하면 아래와 같다.
RxSwift를 사용할 경우 데이터를 다운받아오는 비동기적인 로직과 UI를 그리는 직렬작업 처리를 직접 코드로 구현해주는 것이 아닌 단순히 클래스 및 함수 명명으로 간단하게 구현할 수 있다는 것이다.
즉, 코드의 양을 상당히 줄이고, 함수를 통해서 연쇄적인 비동기처리를 간단하게 표현할 수 있게 된다.
이말은 즉, RxSwift를 통해 전달받은 데이터를 그냥 사용해도 쓰레드에 문제가 생기지 않아 안전하게 UI 작업을 할 수 있다는 것을 의미한다.
4. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
1) 기본방법
2) 추가 API 적용
위와 같이 기본적인 방법을 사용해서만 사용을 할 경우 코드의 양이 상당히 길어지게 된다. 이에 따라 코드의 양을 줄일 수 있는 방법을 RxSwift 라이브러리에서 API 로 제공하고 있고 그것을 사용하여 아래와 같이 코드의 양을 줄일 수 있다.
해당 API 를 오퍼레이터라고 하며 다양한 오퍼레이트가 있다.
오퍼레이터는 역할에 따라서 다양한 종류가 있다. 이에 따라 중요 오퍼레이터들은 그 쓰임새를 파악할 필요가 있다.
3) Observable 오퍼레이터 (일부)
1. just
단 하나의 데이터를 전달할 경우 just를 사용한다.
2. from
배열안에 있는 항목들을 각각 받고 싶으면 from 오퍼레이터를 사용한다.
5. Observable로 오는 데이터를 받아서 처리하는 방법
1) 기본방법
- 위와 같이 observable을 subscribe 라는 오퍼레이터를 사용하여 구독하고 데이터를 전달 받을 수 있다.
2) 추가 API 적용
1. subscribe(onNext:)
2. observeOn(MainScheduler.instance)
- 쓰레드와 관련된 작업을 처리할 때 사용하는 오퍼레이터다.
6. 쓰레드의 활용과 메모리 관리 (중요 개념)
- Observable의 라이프 사이클은 아래와 같다.
- Subscribe
- Next
- Completed / Error
- Disposabled
Completed 와 Error 가 발생하는 시점에서 Observable의 라이프 사이클은 끝나게 된다.
- 순환참조와 메모리 관리
- Unifinished Observable / Memory Leak
- (참조) 클로져와 메모리 해제 실험
- 쓰레드 분기
- DispatchQueue, OperationQueue
- observeOn, subscribeOn
- Stream의 분리 및 병합
- share
- combine, merge, zip
7. Stream 분리 및 병합
- 오퍼레이터를 통해서 Stream 을 분리 및 병합할 수 있다. 그리고 대표적인 오퍼레이터는 1) Merge 가 있다.
- 스트림 1,2 가 존재한다고 하면 2개의 스트림을 하나로 병합하여 만들어준다. 대표적인 오퍼레이터는 2) zip 이 있다.
- 스트림 1,2 가 존재할 때 1의 데이터와 2의 데이터가 쌍으로 이뤄질 때 해당 쌍으로 이뤄진 값을 새로 생성한 스트림에 떨궈준다. 대표적인 오퍼레이터는 3) CombineLatest 가 있다.
8. UI 컴포넌트와의 연동
1) Subject
- Observable 의 기능을 보완하기 위해 만들어진 것이 Subject 이고 Observable 과 Subject 를 구분 짓는 명확한 기능 차이는 아래와 같다.
- 데이터 조작
- Observable의 문제점은 데이터를 생성할 수는 있지만 값을 조작하는 것은 안되는 단점이 있다. 이것을 보완해주는 기능이 Subject 이다.
- Subject 를 사용할 경우 값을 생성할 뿐만 아니라 조작까지 할 수 있게 된다.
- Multi-Cast
- Observable 의 경우 하나의 Observer 만이 구독이 가능하지만 Subject 의 경우 다양한 Observar 들이 구독이 가능하다.
- 데이터 조작
2) Relay
- Subject의 이래와 같은 단점을 보완하기 위해서 만든 클래스이다.
- Subject의 경우 비동기 처리를 하던 도중 에러가 발생하면 Subject의 스트림이 끊기는 현상이 발생하면서 해당 새로운 Subject 스트림들 생성하게 된다. 이에 따라 이전 스트림에서 전달한 데이터를 받을 수 없게 된다. 하지만 UI 작업의 경우 이전에 작업했던 데이터들을 전달받아야 하므로 이전에 작업했던 데이터들을 새 스트림 생성시 그대로 가져오는 RelaySubject라는 클래스를 만들어 놓았다.
3) RxCocoa
- RxCocoa 는 UI를 그리는 작업을 하므로 이전에 아래 두 코드는 무조건적으로 구현해줘야 한다.
- catchErrorjustReturn("") 코드는 비동기 처리에서 에러가 전달될 경우 어떻게 처리할 것인지를 결정하는 오퍼레이터이다.
- observeOn(MainScheduler.instance) 코드는 메인스레드에서 UI 작업을 처리하기 위해서 구현하는 오퍼레이터이다.
- 위 코드를 한번에 처리하는 오퍼레이터들은 UI를 그리기 전에 항상 사용을 해야하는 것들이라서 이것을 하나로 묶은 오퍼레이터가 있다.
- asDriver(onErrorJustReturn: "") 는 에러 처리를 하는 오퍼레이터이다.
- drive(itemCountLabel.rx.text) 는 메인스레드에서 전달된 데이터를 변수로 전달된 뷰에다 그리는 오퍼레이터 이다.
4) 요약
- UI 작업의 특성에 맞게 Observable 및 Subject 를 기능 개선한 것들이 아래와 같은 클래스들이다.
- Driver → Observable 을 개선한 것
- Relay → Subject 를 개선한 것
9. 프로젝트에 RxSwift + MVVM 적용하기
1) MVVM 아키텍처
- 어플리케이션 아키텍처
- 왜 MVVM을 쓰는가?
- MVC → MVP → MVVM 과 같이 아키텍처가 개선이 진행되었다.
- MVC 의 경우 ViewController 에서 Model을 가공하는 로직을 구현하고 이를 사용하여 View에 그려는 작업을 한다. 그렇다 보니 ViewController 내부에 View 를 그려주는 로직, Model을 View에 그려주기 편하도록 가공하는 작업을 모두 해주게 된다.
하지만 위와 같이 코드를 구현할 경우 ViewController 내부에 너무 많은 로직이 구현되므로 Massive 한 ViewController 가 만들어진다. - 이를 개선하기 위해서 MVP 가 만들어졌다. View를 그리는 로직을 View와 ViewController 가 하도록 하고 Model 을 가공하는 작업을 Presenter 가 하도록 만들어줬다. 이렇게 역할을 구분함에 따라 ViewController 내부에서 구현된 코드의 양을 줄일 수 있고, Model 을 가공하는 부분을 테스터블 하게 사용할 수 있게 되었다.
- 위와 같은 MVP의 문제를 해결한 것이 MVVM 이다. MVVM은 하나의 ViewModel을 통하여 여러개의 View에서 공통으로 사용하는 Model을 가공하고 그것이 변경될 경우 각 View가 변하도록 구현해놓았다.
- MVVM과 RxSwift의 꿀조합