반응형 프로그래밍은 시간에 따라 변하는 데이터 흐름(stream)을 관찰(observer)하여 자동으로 반응하는 방식의 프로그래밍이다. 선언적으로 이벤트를 처리할 수 있어 코드가 깔끔하고 유지보수가 쉽다.
RxSwift는 Swift에서 반응형 프로그래밍을 구현할 수 있게 해주는 라이브러리다. Observer, Observable, Operator로 구성되어 있다.
// 숫자 1, 2, 3이 순서대로 나오는 데이터 스트림
let stream = Observable.of(1, 2, 3)
// Observer가 구독을 시작해서, Observable이 흘려보내는 값에 반응하는 모습
stream.subscribe(onNext: { value in
print("받은 값: \(value)")
})
Observable.of(1, 2, 3, 4, 5)
.filter { $0 % 2 == 0 } // 필요한 데이터(짝수)만 통과
.map { "숫자: \($0)" } // 데이터 변형(문자열로 변환)
.subscribe(onNext: { print($0) })
/* 결과
숫자: 2
숫자: 4
*/
| 개념 | 설명 | 비유 | 예시 코드 |
|---|---|---|---|
| Observable | 데이터 흐름 | 수도꼭지 | Observable.of(1, 2, 3) |
| Observer | 데이터를 받아 반응 | 컵 | .subscribe(onNext: { ... }) |
| Operator | 연산·변형·필터링 수행 | 정수기 필터 | .map, .filter, .merge 등 |
Observable.create, URLSession📺 넷플릭스
넷플릭스에서 영화를 틀면, 그 영화는 나만을 위해 재생된다.
다른 사람은 독립적으로 다른 타이밍으로 재생된다.
let cold = Observable<Int>.create { observer in
print("📺 Cold Observable 시작")
observer.onNext(1)
observer.onNext(2)
observer.onCompleted()
return Disposables.create()
}
cold.subscribe { print("👀 A: \($0)") }
cold.subscribe { print("👀 B: \($0)") }
/* 결과 -> A와 B는 서로 다른 스트림을 받음
📺 Cold Observable 시작
👀 A: next(1)
👀 A: next(2)
👀 A: completed
📺 Cold Observable 시작
👀 B: next(1)
👀 B: next(2)
👀 B: completed
*/
Subject, NotificationCenter, Timer📻 라디오 생방송
라디오는 9시에 생방송 시작되며, 9시 5분에 켠 사람은 앞부분을 놓친다.
방송은 멈추지 않고 계속 흘러간다.
let subject = PublishSubject<String>()
subject.onNext("🎙️ 생방송 시작") // 아직 아무도 듣지 않음
subject.subscribe { print("👀 A: \($0)") }
subject.onNext("📰 뉴스1")
subject.onNext("📰 뉴스2")
subject.subscribe { print("👀 B: \($0)") }
subject.onNext("📰 뉴스3")
/* 결과 -> B는 뉴스3부터 수신 (뉴스1, 2는 못 봄)
👀 A: next(📰 뉴스1)
👀 A: next(📰 뉴스2)
👀 A: next(📰 뉴스3)
👀 B: next(📰 뉴스3)
*/
💡 언제 어떤 걸 사용하는 것이 좋을까?
- 네트워크 요청, 버튼 클릭 후 동작 → Cold Observable
- 실시간 이벤트(UI, Notification), 소켓, 스트리밍 데이터 → Hot Observable
Observable + Observer를 합친 것이다.
Observable: 데이터를 방출하는 입장Observer: 데이터를 받는 입장Subject: 데이터를 보내기도 하고, 받기도 함🎙️ 마이크 + 스피커
Subject는 마이크처럼 데이터를 받아서, 동시에 스피커처럼 다른 사람들에게 데이터를 방출let subject = PublishSubject<String>() // 1. Subject 생성 subject.onNext("🎤 안녕하세요") // 2. 데이터를 흘림 (마이크 역할) subject.subscribe(onNext: { value in // 3. 구독자 등장 (스피커) print("👂 \(value)") })
새로 구독한 이후부터 데이터 수신
let subject = PublishSubject<String>()
subject.onNext("🔥 방송 시작 전")
subject.subscribe(onNext: { print("👀 A: \($0)") })
subject.onNext("📰 뉴스1")
subject.onNext("📰 뉴스2")
/*결과 -> 구독 후 데이터만 받음
👀 A: 📰 뉴스1
👀 A: 📰 뉴스2
*/
가장 최근 값 + 이후 값 수신
let subject = BehaviorSubject(value: "🍎 기본값")
subject.onNext("🍌 바나나")
subject.subscribe(onNext: { print("👀 A: \($0)") })
subject.onNext("🍇 포도")
/*결과 -> 가장 최근 값부터 받음
👀 A: 🍌 바나나
👀 A: 🍇 포도
*/
과거 n개의 값 + 이후 값 수신
let subject = ReplaySubject<String>.create(bufferSize: 2)
subject.onNext("📺 에피1")
subject.onNext("📺 에피2")
subject.onNext("📺 에피3")
subject.subscribe(onNext: { print("👀 A: \($0)") })
/* 결과 -> 지정한 개수만큼 과거 값도 보여줌
👀 A: 📺 에피2
👀 A: 📺 에피3
*/
작업 완료 후 마지막 값 1개만 수신
let subject = AsyncSubject<String>()
subject.onNext("💡 준비")
subject.onNext("💡 거의 다 됨")
subject.onNext("💡 완료 직전")
subject.subscribe(onNext: { print("👀 A: \($0)") })
subject.onNext("💡 진짜 완료")
subject.onCompleted()
/* 결과 -> onCompleted()가 호출되어야만, 마지막 값 1개를 전달
👀 A: 💡 진짜 완료
*/
RxSwift에서 Observable을 subscribe()하면 Disposable이라는 토큰이 반환된다. Disposable을 dispose() 하거나, DisposeBag에 넣으면 구독이 종료(해제)된다.
// disposeBag이 deinit(해제)되는 순간 자동으로 모든 구독이 종료
import RxSwift
let disposeBag = DisposeBag()
Observable.of("1", "2", "3")
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
let subscription = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
.subscribe(onNext: { print("⏱️ \($0)") })
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
subscription.dispose() // 5초 뒤 직접 구독 취소
print("🛑 구독 종료")
}
// `DisposeBag`처럼 여러 구독을 묶어놓고 해제할 수 있는 도구
let compositeDisposable = CompositeDisposable()
let disposable1 = Observable.of(1, 2).subscribe(onNext: { print("첫 번째: \($0)") })
let disposable2 = Observable.of("A", "B").subscribe(onNext: { print("두 번째: \($0)") })
_ = compositeDisposable.insert(disposable1)
_ = compositeDisposable.insert(disposable2)
// 나중에 한 번에 해제 가능
compositeDisposable.dispose()