[TIL] 반응형 프로그래밍

박주하·2025년 6월 5일

1. RxSwift


반응형 프로그래밍은 시간에 따라 변하는 데이터 흐름(stream)을 관찰(observer)하여 자동으로 반응하는 방식의 프로그래밍이다. 선언적으로 이벤트를 처리할 수 있어 코드가 깔끔하고 유지보수가 쉽다.
RxSwift는 Swift에서 반응형 프로그래밍을 구현할 수 있게 해주는 라이브러리다. Observer, Observable, Operator로 구성되어 있다.


1. ⛲️ Observable (데이터의 흐름)

  • 데이터(이벤트)가 시간에 따라 흘러나오는 일종의 데이터 스트림
  • 수도꼭지를 틀면 물이 흐르듯, Observable도 subscribe(구독)하면 데이터가 흐르기 시작
// 숫자 1, 2, 3이 순서대로 나오는 데이터 스트림
let stream = Observable.of(1, 2, 3)

2. 🥤 Observer (관찰자)

  • Observable에서 나오는 데이터를 받아들이고 반응하는 주체
  • 수도꼭지에서 흐르는 물을 받는 컵 같은 존재
// Observer가 구독을 시작해서, Observable이 흘려보내는 값에 반응하는 모습
stream.subscribe(onNext: { value in
    print("받은 값: \(value)")
})

이벤트의 종류

  • onNext: 아이템 방출
  • onCompleted: complete 되었을 때 방출
  • onError: 에러 발생했을 때 방출
  • onDisposed: 옵저버가 처분되었을 때 방출

3. 🔧 Operator (데이터 가공 도구)

  • Observable에서 나오는 데이터를 필터링, 변경, 조작하는 기능
  • 수도꼭지 물이 나오기 전에 필터를 걸어 깨끗하게 하듯이, 데이터 스트림에도 연산 적용 가능
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 등

2. Cold Observable vs Hot Observable


1. ❄️ Cold Observable

  • 구독할 때마다 새롭게 시작되는 스트림
  • 각 구독자마다 처음부터 독립적으로 데이터를 받음
  • 예: 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
*/

2. 🔥 Hot Observable

  • 이미 흘러가고 있는 데이터 스트림
  • 구독자가 나중에 들어와도 지금 상태부터 수신(이전 데이터는 못 받음)
  • 여러 구독자가 동시에 같은 스트림을 공유
  • 예: 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

3. Subject


Observable + Observer를 합친 것이다.

  • Observable: 데이터를 방출하는 입장
  • Observer: 데이터를 받는 입장
  • Subject: 데이터를 보내기도 하고, 받기도 함

🎙️ 마이크 + 스피커

  • Subject는 마이크처럼 데이터를 받아서, 동시에 스피커처럼 다른 사람들에게 데이터를 방출
let subject = PublishSubject<String>() // 1. Subject 생성
subject.onNext("🎤 안녕하세요")           // 2. 데이터를 흘림 (마이크 역할)
subject.subscribe(onNext: { value in   // 3. 구독자 등장 (스피커)
    print("👂 \(value)")
})

1. PublishSubject

새로 구독한 이후부터 데이터 수신

let subject = PublishSubject<String>()

subject.onNext("🔥 방송 시작 전")

subject.subscribe(onNext: { print("👀 A: \($0)") })

subject.onNext("📰 뉴스1")
subject.onNext("📰 뉴스2")

/*결과 -> 구독 후 데이터만 받음
👀 A: 📰 뉴스1
👀 A: 📰 뉴스2
*/

2. BehaviorSubject

가장 최근 값 + 이후 값 수신

let subject = BehaviorSubject(value: "🍎 기본값")

subject.onNext("🍌 바나나")

subject.subscribe(onNext: { print("👀 A: \($0)") })

subject.onNext("🍇 포도")

/*결과 -> 가장 최근 값부터 받음
👀 A: 🍌 바나나
👀 A: 🍇 포도
*/

3. ReplaySubject

과거 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
*/

4. AsyncSubject

작업 완료 후 마지막 값 1개만 수신

let subject = AsyncSubject<String>()

subject.onNext("💡 준비")
subject.onNext("💡 거의 다 됨")
subject.onNext("💡 완료 직전")

subject.subscribe(onNext: { print("👀 A: \($0)") })

subject.onNext("💡 진짜 완료")
subject.onCompleted()

/* 결과 -> onCompleted()가 호출되어야만, 마지막 값 1개를 전달
👀 A: 💡 진짜 완료
*/

4. 구독 취소


RxSwift에서 Observable을 subscribe()하면 Disposable이라는 토큰이 반환된다. Disposable을 dispose() 하거나, DisposeBag에 넣으면 구독이 종료(해제)된다.

1. DisposeBag: 자동 구독 해제

// disposeBag이 deinit(해제)되는 순간 자동으로 모든 구독이 종료
import RxSwift

let disposeBag = DisposeBag()

Observable.of("1", "2", "3")
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

2. 수동으로 dispose() 호출

let subscription = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
    .subscribe(onNext: { print("⏱️ \($0)") })

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    subscription.dispose() // 5초 뒤 직접 구독 취소
    print("🛑 구독 종료")
}

3. CompositeDisposable로 여러 구독 관리

// `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()

0개의 댓글