RxSwift 4시간 만에 끝내기
요즘 핫하다는 RxSwift에 대해서 알아보자
처음부터 디테일 한 부분을 꼼꼼하게 이해하기 보다 한번 큰 그림을 싹 훑고나서 상세한 부분을 보고자 함.
(주의)
- 해당 포스팅은 정보 전달 목적이 아닌 내가 학습한 내용을 그냥 단순히 기록하기 위한 용도라 틀린 정보 있을 수 있음,
공식문서 링크
RxSwift에서 사용되는 오퍼레이터들이 어떻게 동작하는지에 대한 부분
http://reactivex.io/documentation/ko/observable.html
ReactiveX - Observable
Observable ReactiveX에서 옵저버는 Observable을 구독한다. Obseravable이 배출하는 하나 또는 연속된 항목에 옵저버는 반응한다. 이러한 패턴은 동시성 연산을 가능하게 한다. 그 이유는 Observable이 객체를
reactivex.io
목차
1. Observable 사용법
2. Operator - just, from, subscribe
3. Observable 생명주기, Thread 관리
4. Subject - Observable 외부에서 값을 컨트롤 할 수 있을까?
5. RxCocoa를 이용해 UI를 컨트롤 해보자.
6. 추가로 조금 더 알아보자
Observable 에 대한 기본적인 사용법 흐름 정리
- 생성: Observable create
- 구독: subscribe로 이벤트 스트림 구독
- 해제: Disposable로 구독 해제 (관찰 해제)
크게는 3가지 형태로 흘러감
RxSwift를 사용하는 이유
RxSwift 는 비동기 작업의 결과를 Completion closure 가 아닌, 함수의 return 값으로 전달하기 위한 유틸리티 중 하나이며, 전달하는 것이 목표
직접 RxSwift를 사용해보면서 RxSwift가 필요한 이유에 대한 생각
- UIKit에서 RxSwift없이 클로저로만 개발할 경우 Snapkit없이 AutoLayout만 사용하겠다는 느낌.
- 물론 불가능한건 아니지만, 프로덕트 만드는데 생산성 측면에서 사용하고 안하고의 차이가 큼.
- Combine으로만 개발하자는 의견에는 현재까지는 RxSwift + RxCocoa를 활용하면 기능이 많고 더 다양함.
- 개인적으로는 SwiftUI + async/await을 보아하니 Flutter처럼 된다면 Combine과 RxSwift 둘 다 필요 없을거 같음.
- UIKit의 경우에는 Combine보다 RxSwift를 적절히 활용할 줄 안다면 이게 더 나음.
- 명확히 ControlEvent 영역은 RxCocoa가 있어서 Combine보다 훨씬 더 나음
- 현 시점에서는 UIKit에서 Rx사용할 줄 안다면 적극적으로 써도 된다고 생각함
- SwiftUI 넘어가면 Combine보다는 async/await이 더 잘어울림.
- 그리고 시기가 변하면서 선택하는게 달라질탠데 Combine보다는 async/await이 되지 않을까란 생각
- SwiftUI에서도 대부분 async/await로 해결되고 있어서 Combine 그렇게 막 다루는 케이스가 있던 것 같지는 않음.
- RxSwift에서 Combine으로 리팩하자는 의견에도 굳이라는 생각이 너무 큼 (다른 중요한 작업에 비해 중요도 낮음.)
Observable이란?
RxSwift 에서 제공하는 "나중에 생기는 데이터" 타입의 이름이 Observable
비동기로 인해 생기는 데이터가 나중에 생기는 데이터인데, 이걸 우리는 completion closure가 아닌 return 값으로 받아서 사용함.
즉, 비동기로 생기는 데이터를 Observable 타입으로 래핑해서 사용하는 방법은 어떻게 될까?
Observable의 생명주기
- 1. Create
- 2. Subscribe -> 이거 되었을 때 동작한다. (실행된다는 의미)
- 3. onNext
- 종료: Observable은 재사용이 불가능함.
- 4. onCompleted / onError
- 5. Disposed
위와 같은 순서로 진행
RxSwift를 활용해 네트워킹하는 예제
서버로 부터 데이터를 받아 처리하는 코드
final class ImageService {
private var disposeBag = DisposeBag()
private func loadImage(from url: String) -> Observable<UIImage?> {
return Observable.create { emitter in
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _, error in
if let error = error {
emitter.onError(error)
return
}
guard let data = data, let image = UIImage(data: data) else {
emitter.onNext(nil)
emitter.onCompleted()
return
}
emitter.onNext(image)
emitter.onCompleted()
}
task.resume()
return Disposables.create {
task.cancel()
}
}
}
}
- 코드를 천천히 살펴보면 Ovservable로 감싸서 타입을 반환하고 있음
- 이렇게 해야 추상화되어서 여기저기 전달할 수 있음.
- Observable을 Create하고 emitter를 통해 실제 데이터를 방출
- 완료되면 onCompleted 호출 혹은 에러나면 onError 호출
- 참고로 onError가 발생하면 onCompleted가 발생해서 스트림이 종료됨 <- 중요
- 사용이 끝나면 반드시 Disposable 해야함.
- 이거 안하면 메모리 누수날 수 있음.
- ARC에 의해서 ref_count가 감소하지 않기 때문에
Observable Operator (just, from, subscribe)
아래 코드는 Observable을 기본적으로 사용하는 코드.
이를 다른 오퍼레이터로 개선하고자 함.
func downloadJson(_ url: String) -> Observable<String?> {
Observable.create { emitter in
emitter.onNext(data)
emitter.onCompleted()
return Disposables.create()
}
}
데이터의 갯수가 하나라면 just를 통해서 처리할 수도 있다.
just의 경우에는 단일 항목으로 내려보낸다.
func downloadJson(_ url: String) -> Observable<String?> {
return Observable.just(data)
}
데이터가 그러면 여러개라면 from을 통해서 처리할 수 있다.
from의 경우에는 배열 처럼 여러개의 항복을 내려보낼 수 있다.
func downloadJson(_ url: String) -> Observable<String?> {
return Observable.from([data1, data2])
}
이번에는 subscribe에 대해서 볼 것인데, 원래는 observable create를 명시적으로 생성해 사용했지만
함수로 분리해서 사용할 수도 있음
_ = downloadJson(MEMBER_LIST_URL)
.subscribe { event in
switch event {
case .next(let json):
// main queue 에서 UI 작업 등
case .completed:
break
case .error:
break
}
}
이렇게 사용할 수 있다. 위에 작성한 함수를 subscribe(구독) 해서 사용하면 된다.
그리고 event가 중요한데, 저 함수에 subscribe를 설정하면, 비동기적으로 event가 발생한다. 이걸 3가지 옵션에 따라 처리할 수 있음.
구독했으면 참고로 Disposed 해야함
_ = downloadJson(MEMBER_LIST_URL)
.subscribe(onNext: {print($0)})
Observable 생명주기 정리
- 함수로 사용할 때는 Create를 통해 사용하는게 아니고 subscribe를 통해 구독해서 사용
- Completed, Error 가 발생한 후에는 재사용할 수 없음
- Completed or Error 로 끝나도 Disposed 되고, 중간에 취소되도 마찬가지
순환참조
Rx를 사용하게 되면 메모리 누수가 발생할 수 있다. 특히 Reference Cycle에 의해서 !
Observable 을 실행한 후, Completed / Error / Disposed 로 끝내게 되면 Observable 이 사라지게 되므로, 참조가 사라져 메모리가 해제된다.
Subscribe를 통해 사용하게 된다면 내부 구현이 클로저로 self를 캡처하는 형태이기 때문에 ARC에 대한 관리가 필요하다
weak, unowned 등을 대안으로 활용할 수 있고 경우에 따라서는 필요한 값만 복사해서 사용할 수 있다.
RxSwift 6.5 이상 버전에서는 bind(with: )이라는 문법을 사용할 수도 있음.
items.rx
.bind(with: self) { this, item in
this.configure(item)
}.dispseBag()
Thread 관리
RxSwift에서 사용하는 스케줄러를 통해 전환하는건 observeOn, subscibeOn 2개가 존재한다.
- observeOn: Observable이 생성되는 시점의 스레드를 바꿈. (upstream 적용)
- 즉, Observable 생성 ~ 이벤트 방출 전까지의 흐름의 스레드
- 호출 위치에 영향을 받음
- subscibeOn : 이벤트를 전달받는 시점의 시점의 스레드를 바꿈. (downstream 적용)
- 즉, 이 코드는 호출하는 시점과 상관없이 이후 연산자들과 구독자
- 이 스레드가 사용되는 시점에서 변경한다는 의미라 호출 위치가 중요하지 않음.

위의 사진을 통해 스레드 흐름이 변경되는걸 직관적으로 이해할 수 있음.
스트림(stream)의 병합.
옵저버블을 사용하다 보면은 옵저버블을 여러개를 사용하게 되고, 이를 통합해야할 필요가 있을 때가 있다. 우리는 그럼 어떻게 해야할까?
- Merge : 여러개의 독립적인 옵저버블을 하나의 옵저버블에 합쳐서 보여주는 것이다.
- 데이터 타입이 같아야만 사용할 수 있다.

- Zip : 독립적인 옵저버블을 합쳐주는 것인데, 아래의 그림을 보다시피, 두개의 독립적인 스레드에서의 값이 1:1 매핑되어야 아래로 내려오는 모습을 볼 수 있다.
- 어떻게 쌍으로 묶을지는 개발자가 지정한다. 그러므로 양 쪽의 Observable 의 데이터 타입이 달라도 된다.

- CombineLatest : 보다시피 아래의 조건에 맞게끔 설정되어 나오는데, 데이터가 1:1 매핑이 아니여도 내려보내주는데, 다른 옵저버블 쪽에서 데이터 정보를 받아다가 그대로 내려준다는 장점이 있다. (가장 많이 사용함)

disposed(by: disposeBag) : Disposable은 외부에 저장한 변수로 따로 마련해서 viewWillDisAppear 시점 등을 활용해 특정 작업이 실행되고 있는 중에도 취소시킬 수 있음.
DisposeBag을 활용해 배열로 만들 수 있음.

Subject를 통해 multicast
공식 문서의 링크
http://reactivex.io/documentation/ko/subject.html
ReactiveX - Subject
만약, Subject를 정의했는데, 이를 Subscriber 인터페이스 없이 다른 에이전트에 전달하고 싶다면 그 Subject를 순수 Observable로 리턴하는 asObservable 메서드를 사용하면 된다. 참고
reactivex.io
Observable 을 이용해서 UI를 업데이트하려고 했지만, viewDidLoad() 에서 생성한 Observable 은 onOrder() 함수에서 소비되어서 활용이 불가능함.
> 즉, unicast라는 의미
subject를 통해 multicast를 구현해 내가 생성하고 나만 소비하는 것이 아닌 다른 사람에게도 이벤트를 전달하고자 함.
subject를 통해 여러 곳에서 구독해서 처리할 수 있음.
shared를 통해 같은 이벤트를 전달하는 기법도 중요함.

RxCocoa를 이용해 UI를 컨트롤 해보자.
- RxCocoa란 UIKit의 Extension을 추가해 터치 이벤트 등을 받음.
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'RxSwift+MVVM' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for RxSwift+MVVM
pod 'RxSwift'
pod 'RxCocoa'
pod 'RxRelay'
pod 'RxOptional'
pod 'RxViewController'
target 'RxSwift+MVVMTests' do
inherit! :search_paths
pod 'RxTest'
pod 'RxBlocking'
end
end
위의 코드는 실습에서 사용한 Podfile 코드이다.

.bind(to:itemCountLabel.rx.text)
.subscribe { self.itemCountLabel.text }
이렇게 축약이 가능한데, 이렇게 사용가능하고 테이블 뷰를 구성하는 셀도 축약이 가능해서 이렇게 되면, dataSource를 연결할 필요가 없어짐
2022.08.21 추가
현재는 RxSwift를 통해 위처럼 코드를 작성하지 않음.
- DiffableDataSource와 RxCocoa를 같이 활용할 때 크래시가 발생
- Swift Concurrency 및 ReactorKit / TCA를 참고하면서 모바일 개발이 웹스럽게 바뀌고 있음.
- SwiftUI도 보니까 웹스럽게 바뀌는 느낌이라 RxSwift는 차후를 위해 적용하지 않음.
- 그리고 그냥 cell에는 Rx없이 클로저 활용해서 작업해둬야 Rx에 대한 의존도 낮아지고 인간공학적임
- 다른 사람이 내 셀 재활용하는데 Rx를 모르는 사람이 쉽게 연결하기가 어려워서 셀 정도는 그냥 안쓰는걸로 결정.
추가로 조금 더 알아보자



didSet의 경우 프로퍼티 값이 변경이 완료된 후 실행되는 부분

(참고)
https://www.notion.so/MVVM-RxSwift-da9fa84cd45744d4bea4fcb79269f3a1
MVVM & RxSwift
[1교시] Observable 사용법
www.notion.so
https://levenshtein.tistory.com/452
[RxSwift] Observable, Subscribe
안녕하세요 Hani입니다. 비동기 처리를 위한 RxSwift에 대한 공부를 시작해보겠습니다. 😎 먼저, 앞으로 볼 화면이 어떻게 구성되어있는지 보겠습니다. 위 화면은 아래와 같이 동기적으로 작성되
levenshtein.tistory.com
https://www.notion.so/Rxswift-MVVM-20917b6cfb8c4cf592eeeabe62e501a4
Rxswift+MVVM
#1
www.notion.so
https://jinshine.github.io/2019/01/02/RxSwift/2.Observable%EC%9D%B4%EB%9E%80/
[RxSwift] Observable이란 - (2) - jinShine
Observable이란 observable, observable sequence, sequence라는 표현을 쓰는데 사실 다 같은 말입니다. 중요한것은 이벤트가 비동기적으로 생성하는 기능 이라는것이고, 계속해서 이벤트를 생성하는데 이러한
jinshine.github.io
'apple > RxSwift, ReactorKit' 카테고리의 다른 글
| RxSwift 스터디 5주차 Filtering Observables (0) | 2021.07.10 |
|---|---|
| RxSwift 스터디 4주차 Subject (0) | 2021.07.10 |
| RxSwift 스터디 3주차 Creating Observables (0) | 2021.07.09 |
| RxSwift 스터디 2주차 Observable (0) | 2021.07.08 |
| RxSwift 스터디 1주차 Hello RxSwift (0) | 2021.07.07 |