RxSwift

hyun·2025년 7월 23일
1

iOS

목록 보기
33/54

RxSwift 네이놈. 널 정복하고 말테야.

 RxSwift

비동기 이벤트 처리 라이브러리

시간에 따라 바뀌는 값을 깔끔하게 처리할 수 있도록 도와주는 도구

앱을 만들다 보면

버튼을 눌렀을 때 숫자가 오르는 걸 만들어야 하거나

서버에서 데이터를 받아서 화면에 보여줘야 할 때가 생기거나

유저가 텍스트필드에 텍스트를 입력할 때마다 자동 검색이 되어야 할 때가 있을 수 있는데

이런 이벤트들은 시간에 따라 발생하고 인풋에 따른 원하는 행동을 해야 함
이걸 예쁘고 한눈에 보이게 도와주는 게 RxSwift 인데

일반적인 코드에서는

var count = 0

@IBAction func buttonTapped() {
    count += 1
    label.text = "\(count)"
}

요로코롬 버튼을 눌렀을 때 숫자가 올라가고 라벨이 바뀌게 되는 코드인데
이 정도는 뭐 지금 딱 봤을 땐 깔끔해 보일 수 있지만

만약에 버튼도 여러 개에 여러 뷰도 바꿔야 하고 서버 응답도 받아야 한다면

콜백 지옥에 상태는 꼬일대로 꼬이고 버그가 난무하는 현상이 생길 수도 있음

RxSwift를 사용하면

button.rx.tap
    .scan(0) { count, _ in count + 1 }
    .map { "숫자: \($0)" }
    .bind(to: label.rx.text)
    .disposed(by: disposeBag)

버튼이 눌릴 때마다 → 숫자를 하나씩 더하고 → 문자열로 바꿔서 → 라벨에 표시하는 이벤트 흐름을 한 줄로 표현할 수가 있음

 4가지 기능 일단 이해만 해보자

1. Observable

계속해서 값을 만들어내는 공장? 느낌이라고 생각하면 됨
데이터를 시간 흐름에 따라 계속 내보내는 애

Observable은 이벤트나 데이터를 흘려보내는 통로 같은 것 !
Observable이라는 친구의 개념을 처음 배울 때 데이터가 흐르는 통로? 라는 공통점 때문인지 시스템 버스가 생각 났는데 덕분에 이해하기 쉬웠던 거 같음

위에 언급했던 예를 가져와보면

버튼을 누를 때마다 눌림 이벤트 발생
서버에서 데이터가 오면 응답 이벤트 발생
타이머가 1초마다 숫자 방출

시간에 따라 일어나는 변화(이벤트)를 계속해서 방출해주는 역할

let stream = Observable.of(1, 2, 3)

이렇게 만들면 1 → 2 → 3 이라는 값이 차례로 나오게 됨

2. Observer

공장에서 나온 데이터를 받는 소비자
Observable이 보내는 값을 구독해서 반응하는 애

Observable이 계속 물건(데이터)을 만들면
Observer는 그걸 구독해서 받아오고 처리하게 됨

Observable.of("🐶", "🐱", "🐰")
    .subscribe(onNext: { animal in
        print("동물 받음: \(animal)")
    })

저렇게 짜게 됐을 때 구독자가 동물이 올 때마다 콘솔에 찍게 됨

3. Operator

중간에서 데이터를 가공하거나 필터링하는 거
Observable에서 나오는 값을 수정||변경해주는 애들

Observable이 데이터를 흘려보낼 때 당연히 중간에서 가공/필터링/변환할 수 있을텐데 이걸 도와주는 친구

Observable.of(1, 2, 3, 4)
    .map { $0 * 2 }
    .filter { $0 > 4 }
    .subscribe(onNext: { print($0) }) // 6, 8 출력

.map { $0 * 2 } → 값을 2배로 바꿈

.filter { $0 > 4 } → 4보다 큰 값만 통과

이렇게 Operator는 중간에서 일하는? 역할

4. DisposeBag

구독을 정리하는 휴지통
메모리 누수를 막기 위해서 구독을 해제할 때 사용

Observer가 Observable을 구독하면 구독된 상태로 계속 살아있게 됨
뷰가 사라졌는데도 계속 구독하고 있으면 뷰 컨트롤러도 메모리에서 안 사라지게 될 거고 그렇게 되면 메모리 누수가 될 것임 점점 쌓이다 보면 앱이 터질수도..

그래서 DisposeBag이라는 휴지통에 구독들을 담아두고
화면이 사라질 때 한꺼번에 정리하게 됨 (휴지통 비우는 느낌이라 생각하면 될 듯)

let disposeBag = DisposeBag()

Observable.of("🤓", "🥸")
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

이러면 뷰가 없어질 때 자동으로 정리되게 될 것임

 RxSwift 흐름

ObservableOperatorObserver
(이벤트 발생)(변경/가공)(값 받기)

버튼 누를 때마다 라벨 숫자 증가하는 걸 만들어 봄

button.rx.tap
    .scan(0) { count, _ in count + 1 }
    .map { "현재 숫자: \($0)" }
    .bind(to: label.rx.text)
    .disposed(by: disposeBag)
  • .rx.tap → 버튼 눌림 이벤트를 스트림으로 만들어줌 (Observable)

  • .scan(0) → 누적 숫자 카운트 (Operator)

  • .map → 문자열로 변환 (Operator)

  • .bind(to:) → 라벨에 바로 바인딩 (Observer)

  • .disposed(by:) → 메모리 정리 (disposeBag)

이렇게 한 줄씩 이벤트를 이어서 쓰는 걸 체이닝 이라고 하는데
이게 RxSwift 장점이랑 연결 됨


 이제 뭐가 있는 지 알아야 써먹지

모든 상황에서 Observable 하나로 처리하면 가능은 할 수 있겠지만 복잡해지고 위험할 수 있음 상황에 따라서 공장에서 적절하게 기능을 제공하려면 Observable 안에서도 종류로 나뉘어야 함

 Observable 타입

  • Observable
    계속해서 여러 번 값을 보내는 스트림

뉴스레터 같은 거 구독하면 뉴스 뜰 때마다 계속 메일에 알림이 오도록 하는데 그런 거? 무제한으로 계속 값을 보낼 수 있음

  • Single
    한 번만 값을 보내고 끝나는 스트림

마침 오늘 옷을 샀기 때문에 택배 도착 하는 걸로 이해해보면 택배가 도착했는지 도착하지 않았는지, 그러니까 하나의 결과 (성공 or 실패)만 알려줌 !
Completable 쓰고 보니까 이렇게만 예를 들면 Completable랑 좀 헷갈릴 거 같은데
택배가 도착했는지 여부만 중요하고 안에 뭔지는 관심 없을 땐 Completable에 가깝겠지만
도착했는데 안에 뭐가 들었는지도 알려준다면.. Single의 예시가 됨.
예를 들어 티셔츠가 안에 들어있다는 걸 알고 있다면 Single 임

Single<String>.just("티셔츠 도착!")

이런 식으로.

  • Completable
    성공이나 실패만 알리고 값은 없음

파일을 저장할 때 저장 완료 했는지 or 실패했는지 알려주는 거 생각하면 편할 듯
→ 값은 없고 됐는지만 중요할 때

  • Maybe
    값이 하나 있을 수도 있고 없을 수도 있음

택배함 열었는데 도착한 택배가 있거나 없을 수도 있음

  • Driver
    UI 전용 스트림. 에러도 안 나고 메인스레드에서만 동작
    UI 바인딩할 때 안심하고 쓸 수 있도록 만든 타입

이름 그대로 자동차에 비유하자면 안전 운전??
에러 안 나고 항상 메인(UI)에서 동작해서 걱정 없음!

  • Relay (ex. BehaviorRelay, PublishRelay)
    값을 외부에서 넣을 수 있는 스트림 (Subject 계열)
    onNext 호출 가능 → 값 밀어넣기 가능

내가 직접 값을 넣을 수 있는 통로라고 생각.. 솔직히 이해하는 건 이게 제일 어려웠음 너무 추상적이라 해야하나. 그래서 이건 코드로 직관적이게 비교해보고 싶음

Observable은 값이 흘러나오는 걸 관찰만 할 수 있고 값을 주입하는 건 불가능함

let tapObservable = button.rx.tap // 버튼 눌렀을 때 이벤트

근데 Relay 얘는

let count = BehaviorRelay<Int>(value: 0)

count.accept(10) // <<<<<<<- 여기

이렇게 값을 넣을 수 있음

 Subject

Subject는 Observable이면서 동시에 Observer임
Umm.... 솔직히 저 말을 딱 보기만 했을 땐 뭔지 이해하기 어려운데
Observable은 값을 흘리는 쪽이고 Observer은 값을 받는 쪽인데
Subject는 둘 다 가능한 친구인 셈임
Observable처럼 값을 다른 애한테 전달도 할 수 있고
Observer처럼 다른 Observable에게서 값을 받기도 할 수 있는 것

중계자? 라고 생각하면 될 듯

직접 값을 넣을 수 있는 스트림인 셈

BehaviorSubject

항상 마지막에 emit된 값을 기억하고 있다가 새로운 구독자가 생기면 기억한 값을 전달함

let subject = BehaviorSubject(value: 10)
subject.onNext(20) // 20

초기값 있고 (BehaviorSubject는 만들자마자 기본값을 넣어줘야 함. 아무도 구독하지 않아도 일단 기억되게 됨) 가장 최근 값 1개를 기억하고, 새로운 구독자에게 전달

PublishSubject

let subject = PublishSubject<Int>()
subject.onNext(20) 

초기값 없고, 생기는 값만 전달


 더 알아보자

Hot vs Cold Observable

Observable의 발행 시점이 중요한 상황에서 필요

Cold Observable
구독하는 순간부터 데이터를 보내주기 시작함

구독자가 생겨야 시작
구독한 시점부터 데이터를 받을 수 있음
유튜브 영상처럼 언제 틀어도 처음부터 시작

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

구독하게 되면 1, 2, 3 순서대로 방출

Hot Observable
이벤트가 흘러가고 있으니까 들어오면 그때부터 쭉. 데이터가 구독 여부랑 관계없이 계속 발생

구독 전에도 값이 발생할 수 있고
구독한 시점 이후의 값만 받을 수 있음
생방송 처럼 켜는 순간부터

let subject = PublishSubject<String>()
subject.onNext("🐶") // 이건 구독자 없어도 방출

subject.subscribe(onNext: { print($0) })
subject.onNext("🐱") // 이건 구독자에게 전달

멍멍이는 아무도 못 보고 냥냥이부터 구독자가 볼 수 있음

둘 다 이벤트를 흘려주는 스트림이지만
언제부터 데이터를 보내느냐가 다른 것.

1개의 댓글

comment-user-thumbnail
2025년 7월 25일

정리가 정말 잘되어있네요 👍

답글 달기