[TIL / 25.05.08] RxSwift.. 처음 공부해볼게요 1

2025. 5. 8. 23:18·iOS/Swift

RxSwift란?

RxSwift는 Swift에서 비동기 이벤트를 선언형으로 처리할 수 있게 해주는 프레임워크.

쉽게 말해, 값의 변화와 비동기 흐름을 스트림처럼 다룰 수 있게 해준다.

 

RxSwift Github에선 Reactive Programming in Swift라고 나와 있는데,, 그게 뭐신디요;

 

 

반응형 프로그래밍 (Reactive Programming)

 

반응형 프로그래밍이란, 데이터의 흐름 및 변경사항을 전파하는 데 중점을 둔 프로그래밍 패러다임.

이 패러다임을 사용할 경우, 주변 환경 / 데이터에 변화가 생길 때 연결된 실행 모델들이 이 이벤트를 받아 동작하도록 설계하는 방식

 

a = 10, b = 20이고, c = a + b일 때 c는 30이 될테고 그 뒤에 a를 20을 하던 100000을 하던 c는 변하지 않음.

이미 계산되어 할당됐으니깐!

단, 이 데이터의 흐름을 그리고 이 변경사항을 전파하도록 만들면 a가 20이 된다면 c는 40이 되는거임.

c는 a를 관찰하고, a는 c에게 전파한다! 정도로 일단 넘어가고.

 

그럼 이제 RxSwift에서 중요한 키워드. Observable과 Observer

 

 

Observable

 

관찰이 가능한 흐름, 비동기 이벤트의 시퀀스를 생성할 수 있는 대상.

어느 비동기적인 이벤트가 발생했을 때 데이터의 변화에 대해 관찰이 가능한 형태!

비동기적인 이벤트,,,,가 뭔데 라고 생각할 수 있는데 선언할 당시에는 이 이벤트가 언제 일어날지 모르는 것들을 말함.

 

예를 들면, UIButton의 tap 이벤트. 이건 사용자가 tap을 함으로서 이벤트가 발생하는데 이거 선언 당시에 언제 일어날지 모르잖아요?

 

그래서 이 Observable이 이벤트가 일어나서 데이터의 흐름을 전달하게 되면 이를 받아서 어느 작업을 해주게 될텐데 (아까 예를 들면 a와 b를 더한게 c였던,, 그 c의 업데이트..?) 이 작업을 할 수 있도록 이 Observable을 관찰하는 친구가 Observer임.

 

Observer

 

Observable을 구독하는 친구. 그 Observable이 어느 비동기 이벤트가 실행되어 항목(item)을 방출 했을 때, 그 항목을 받을 수 있음.

이 옵저버블(Observable)을 구독하기 위해선 Subscribe 메서드를 쓸 수 있고, 이 안에선 세 가지 클로저를 파라미터로 넘겨 이벤트를 처리할 수 있음.

 

그 세 가지 파라미터로는 onNext, onError, onCompleted가 있는데,

onNext: 내가 구독하는 Observable이 항목을 방출했을 때 이 클로저가 호출됨. 옵저버블이 방출하는 항목을 가져다가 쓸 수 있음.

onError: 구독하는 옵저버블이 기대되는 데이터가 생성되지 않았거나 다른 이유로 오류가 발생했을 때, 이 오류를 구독자인 옵저버들에게 알리기 위해 이 메서드를 호출함. 그니깐 에러 상황에서 호출된다고 보면됨.

onCompleted: 옵저버블의 이벤트가 종료되어 더이상 호출되지 않을 때, 이벤트가 완료 되었음을 구독자인 옵저버에게 알리기 위해 이 메서드를 호출함.

 

이제 오늘 실습해본 예제로 예를 들자면,

import Foundation
import RxSwift

struct MyError: Error {}

let disposeBag = DisposeBag()

let observable = Observable<String>.create { observer in
    observer.onNext("Apple")
    observer.onNext("Banana")
    observer.onNext("Cake")
    observer.onError(MyError())
    return Disposables.create()
}

observable.subscribe(onNext: { data in
    print("onNext: \(data)")
}, onError: { error in
    print("onError: \(error)")
}, onCompleted: {
    print("onCompleted")
}, onDisposed: {
    print("onDisposed")
}).disposed(by: disposeBag)

 

 

observable은 String 타입의 값을 방출하는 옵저버블타입이고 Apple, Banana, Cake라는 String 값을 방출하게 되어 있음.

이 순서로 방출하고 Error상황일 때 Error도 같이 방출해주도록 설정되었고 

Disposables.create를 통해 Dispose가능한 것을 만들어준다 정도로 이해하면 될 듯.

 

그리고 바로 그 옵저버블을 구독해서 onNext로 data의 이름으로 방출된 아이템을 받게 되면 onNext: \(data)라는 형태로 출력한다고 하고 onError로 에러를 받게 되면 onError: \(error)로 출력하게 됨.

이 모든 이벤트들이 끝나면 onCompleted가 처리되어 해당 프린트문을 실행하고

해당 구독을 더 이상 할 필요 없다고 판단이 될 때 메모리 누수 방지를 위해 구독 해지를 알아서 해줄 수 있도록 disposeBag에 Disposable 객체를 담아둠.

 

만약 이 dispose가 제 때 이뤄지지 않는다면.. 예를 들어 구독한 상태에서 VC가 메모리에서 해제된다고 해보면,,

계속 메모리 누수가 발생하는거임.

 

 

Cold Observable vs Hot Observable

 

Cold Observable은 구독을 했을 때 데이터가 흐르기 시작하고

Hot Observable은 구독과 무관하게 데이터가 흐르는 Observable을 말함.

 

그럼 위 예제에선 구독을 했을 때 맨 첫 방출된 아이템인 Apple부터 Cake까지.. 그리고 Error 등을 그제서야 처리하기 때문에 Cold Observable이라고 할 수 있음.

Observable.create, Observable.just, Observable.of, Observable.from으로 생성한 Observable 예시들은 모두 subscribe를 호출했을 때 그제서야 데이터를 방출하게 되므로 Cold Observable이라고 할 수 있음.

 

 

DisposeBag

 

메모리 누수를 막기 위해 구독을 자동 해제해주는 가방

 

궁금해서 찾아봤는데, DisposeBag에는 Observable을 담는건지, Observer를 담는건지 좀 애매해서 gpt를 돌려보니..

 

✅ DisposeBag에는 옵저버블(Observable)이 아니라 “구독(Disposable)“이 담깁니다.

 

즉, 정확한 대상은:

“Observable을 구독한 결과인 Disposable 객체” 

 

옵저버가 옵저버블을 구독하게 되면 나오는 그 결과인 Disposable이라는데 

 

subscribe 메서드를 들여다보면 Disposable을 반환함을 알 수 있다

 

 

ㅇㅇ.. 어쨋든 이렇게 구독하면 나오게 되는 것들을 가방에 넣어가 구독 해제 관리를 해주는 것!

(DisposeBag이 메모리에서 해제될 때 구독의 해제가 함께 일어난다)


 

그럼 아까 Cold Observable을 말했을 때 나왔던 Observable.~~의 형태를 가지는 옵저버블을 생성하는 방법을 알아보자면

 

Observable.just

 

간단하게 단일 데이터를 방출하는 Observable을 생성할 때는 just를 활용할 수 있다.

struct MyError: Error {}

let disposeBag = DisposeBag()

let observable = Observable.just("무야호")

observable.subscribe(onNext: { data in
    print("onNext: \(data)")
}).disposed(by: disposeBag)

just로 "무야호"라는 값을 방출하는 옵저버블을 생성해 이를 구독하는 예젠데 사실 이를 가져다가 쓰는 옵저버입장에선 onNext로 이벤트 발행을 알게 되면 처리를 하도록 하는건 똑같음. disposed로 메모리 관리해주는것도 같고.

 

Observable.of

 

여러 개의 데이터를 방출하는 Observable을 생성할 때는 of를 활용할 수 있다.

let disposeBag = DisposeBag()

let observable = Observable.of("무한도전", "참 좋아하죠", "무야호~")

observable.subscribe(onNext: { data in
    print("onNext: \(data)")
}).disposed(by: disposeBag)

 

Observable.from

 

배열에서 각 요소를 순차적으로 방출하는 Observable을 생성할 때는 from을 활용한다.

let disposeBag = DisposeBag()

let observable = Observable.from([1, 2, 3, 4, 5])

observable.subscribe(onNext: { data in
    print("onNext: \(data)")
}).disposed(by: disposeBag)

 

 

어 근데 of와 from의 차이를 잘 못느끼겠음. 둘 다 순차적으로 방출하고 여러 데이터를 나열하는거 아닌가?해서 지선생한테 물어봤더니

 

항목 Observable.of Observable.from
입력 형식 인자를 직접 나열 컬렉션 하나를 전달
방출 방식 각 인자를 그대로 순서대로 방출 컬렉션 내부 요소를 하나씩 방출
사용 예 of("A", "B", "C") from(["A", "B", "C"])

 

결국 인자를 직접, 컬렉션 하나를 이 포인트같은데, 맥락과 데이터 형태에 따라 쓰임새가 갈림.

 

Observable.of는 정해진 개수의 값을 간단히 나열하고자 할 때 유용한데, 값을 직접 나열할 수 있을 때 간단히 쓸 수 있고

Observable.from은 컬렉션 데이터를 분해해서 방출할 때 사용하는데, 동적으로 만들어진 리스트에 대응한다고 보면 어느정도 이해가 될 것 같음

 

여기서 또 주의해야 할 게, of는 요소로 배열 하나를 넣어도 그 안에 요소를 순차적으로 방출하는게 아닌 그 배열 하나를 인자 하나로 보기 때문에 of의 처리 방식을 생각하고 구현해야 함.

 

Observable.from

 

일정한 시간 주기로 데이터를 방출하는 Observable을 생성할 때는 interval을 활용한다.

+ 일정한 시간마다 정수 값을 자동으로 0부터 순차적으로 방출함

 

import RxSwift

let disposeBag = DisposeBag()

Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
    .take(5)  // 5개만 받고 끝냄
    .subscribe(onNext: { value in
        print("방출된 값: \(value)")
    }, onCompleted: {
        print("✅ 완료")
    })
    .disposed(by: disposeBag)

print("Observable 시작")

 

지선생한테 받아온 예제인데 1초마다 메인스케줄러에서 5개의 아이템을 받고 스트림을 종료한다는 함수임.

 

이는 실제 사용할 때, 카운트 다운에도 쓰고 스킵 버튼 n초 후 활성화 같은 곳에서도 쓸 수 있다고 함

 


 

Trait

Trait은 Observable 중에서도 특별한 상황에 맞게 제공되는 Observable을 말합니다.

ex. Single, Completable, Maybe

 

왜 쓰는지?

- 더 명확한 의미 부여

- 코드 가독성 향상

- onNext, onCompleted, onError 중 어떤 이벤트가 일어날지 제한적으로 정해져 있음

 

 

Single

 

오직 하나의 값만을 방출하는 Observable을 Single이라고 함.

  • onSuccess: Observable의 onNext와 같은 개념.
  • onFailure: Observable의 onError와 같은 개념.
  • onCompleted는 존재 X, 하나의 값을 방출하거나 에러를 방출하면 곧바로 스트림이 종료됨
let single = Single<Int>.create { observer in
    observer(.success(100))
    return Disposables.create()
}

let single2 = Single<Int>.create { observer in
    observer(.failure(MyError()))
    return Disposables.create()
}

let single3 = Single<Int>.create { observer in
    observer(.success(100))
    observer(.success(200))
    return Disposables.create()
}

single3.subscribe(onSuccess: { data in
    print("onSuccess: \(data)")
}, onFailure: { error in
    print("onFailure: \(error)")
}).disposed(by: disposeBag)

single의 케이스에선 성공할 경우 100이라는 값을 줘라. 그리고 single2는 실패하면 MyError()라는 에러를 방출해라.

그리고 single3에서는 성공시 데이터 방출을 두번 하는데 Single이니 하나의 데이터만 방출하게 돼서 100만 방출하게 됨.

 

observer(.success(), .failure())에 대해서 좀 의아했는데 아마 평소에 success, failure에 관해서

completion handler쓰던거랑 좀 비슷한 맥락인듯?

 

Maybe

 

하나의 값을 뱉거나, 아무것도 뱉지 않고 그냥 complete 되거나, 에러를 방출하는 Observable.

값이 있을 수도 있고 없을 수도 있다 !

  • onSuccess: Observable의 onNext와 같은 개념.
  • onError: Observable의 onError와 같은 개념.
  • onCompleted: Observable의 onCompleted와 같은 개념.

하나의 값을 방출하거나, 에러를 방출하면 곧바로 스트림이 종료된다. 단 값을 방출하지 않고 그냥 complete 될 수도 있다.

 

let disposeBag = DisposeBag()

let maybe1 = Maybe<Int>.create { observer in
    observer(.success(100))
    return Disposables.create()
}

let maybe2 = Maybe<Int>.create { observer in
    observer(.error(MyError()))
    return Disposables.create()
}

let maybe3 = Maybe<Int>.create { observer in
    observer(.completed)
    return Disposables.create()
}

maybe3.subscribe(onSuccess: { data in
    print("onSuccess: \(data)")
}, onError: { error in
    print("onError: \(error)")
}, onCompleted: {
    print("onCompleted")
}).disposed(by: disposeBag)

 

maybe1을 구독했을 땐 성공했다는 이벤트로 100이라는 값을 방출받아 이를 출력할 것이고,

maybe2를 구독했을 땐 에러 케이스로 MyError()를 방출받아 출력하고,

maybe3는 바로 completed로 처리해주었기에 onCompleted에 트리거 돼서 출력하게 됨.

 

Completable

 

값을 뱉지 않고 무언가 완료되는 시점만 알고싶을 때 Completable을 사용함.

  • onError: Observable의 onError와 같은 개념.
  • onCompleted: Observable의 onCompleted와 같은 개념.

유의미한 값은 필요없고 무언가 로딩이 완료되었다 등의 시점이 필요할때 씀.

 

let disposeBag = DisposeBag()

let completable1 = Completable.create { observer in
    observer(.completed)
    return Disposables.create()
}

let completable2 = Completable.create { observer in
    observer(.error(MyError()))
    return Disposables.create()
}

completable2.subscribe(onCompleted: {
    print("onCompleted")
}, onError: { error in
    print("onError: \(error)")
}).disposed(by: disposeBag)

 

completable1은 바로 완료되는 케이스를 completable2는 에러 쪽으로 구성이 되어있고,

이들을 구독하게 된다면 1은 onCompleted, 2는 onError: MyError()이 출력된다.

 

출처 

https://babbab2.tistory.com/182


 

와 앵간치 많이 쓴 것 같은데 한~~참 남았ㄴ.. 그래도 저번에 혼자 블로그만 보고 공부할 때 보단 머리에 들어오는 느낌이 들어 기분 좋네요!

'iOS > Swift' 카테고리의 다른 글

[TIL / 25.05.12] BookSearchApp 트러블슈팅 - 일부 배경색이 투명한 현상  (2) 2025.05.12
[TIL / 25.05.11] RxSwift.. 처음 공부해볼게요 2  (0) 2025.05.11
[WIL / 25.05.06] 8~9주차 회고 및 To-do  (2) 2025.05.06
[TIL / 25.04.17] URLSession으로.. 네트워크 통신을 어떻게 하는지.. 보여줄래요  (2) 2025.04.17
[TIL / 25.04.16] Lv3 서치바 관련 트러블슈팅 기록  (0) 2025.04.16
'iOS/Swift' 카테고리의 다른 글
  • [TIL / 25.05.12] BookSearchApp 트러블슈팅 - 일부 배경색이 투명한 현상
  • [TIL / 25.05.11] RxSwift.. 처음 공부해볼게요 2
  • [WIL / 25.05.06] 8~9주차 회고 및 To-do
  • [TIL / 25.04.17] URLSession으로.. 네트워크 통신을 어떻게 하는지.. 보여줄래요
subkyu-ios
subkyu-ios
subkyu-ios 님의 블로그 입니다.
  • subkyu-ios
    subkyu-ios 님의 블로그
    subkyu-ios
  • 전체
    오늘
    어제
    • 분류 전체보기 (47) N
      • iOS (31) N
        • Swift (31) N
      • 내일배움캠프 (7)
      • Git, Github (3)
      • Algorithm (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    til
    프로그래머스
    algorithm
    최적화
    ios
    Swift
    tabman
    github
    stackview
    사전캠프
    Wil
    알고리즘
    KPT
    내일배움캠프
    본캠프
    트러블슈팅
    회고
    UIKit
    협업
    TableView
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
subkyu-ios
[TIL / 25.05.08] RxSwift.. 처음 공부해볼게요 1
상단으로

티스토리툴바