RxSwift의 사용 방법

maxminseok·2024년 12월 26일
2
post-thumbnail

오늘은 RxSwift 중에서 Observable, Traits, 그리고 자주 사용되는 Operation 연산자들을 배웠다.

ReactiveX(Rx) 는 Microsoft 사에서 만든 라이브러리로, 비동기 프로그래밍옵저버 패턴 을 쉽게 구현할 수 있도록 돕는 라이브러리이다. 데이터의 변화에 반응하는 프로그래밍을 하게되어 반응형 프로그래밍 이라고도 하며,
ReactiveX 를 적용한 Swift 라이브러리를 RxSwift 라고 한다.


Observable 생성 방법

1. Observable.just

  • 특징:
    • 단일 값을 방출한 후 완료 이벤트를 발생시키는 Observable을 생성한다.
    • 초기 값을 고정하거나 테스트 데이터 생성에 유용하다.
  • 사용 예시:
let observable = Observable.just("Max")
observable.subscribe(onNext: { data in
    print("onNext: \(data)")
}).disposed(by: disposeBag)

2. Observable.of

  • 특징:
    • 여러 개의 값을 순서대로 방출한다.
    • 정해진 데이터셋 처리에 적합하다.
  • 사용 예시:
let observable = Observable.of("red", "blue", "yellow")
observable.subscribe(onNext: { data in
    print("onNext: \(data)")
}).disposed(by: disposeBag

3. Observable.from

  • 특징:
    • 배열의 요소를 하나씩 방출한다.
    • 컬렉션 데이터와 함께 자주 사용된다.
  • 사용 예시:
let observable = Observable.from([1, 2, 3, 4, 5])
observable.subscribe(onNext: { data in
    print("onNext: \(data)")
}).disposed(by: disposeBag)

4. Observable.interval

  • 특징:
    • 일정 시간 간격으로 값을 방출하는 Observable을 생성한다.
    • 타이머 구현이나 주기적 이벤트 발생 시 유용하다.
  • 사용 예시:
let scheduler = SerialDispatchQueueScheduler(qos: .default)
let observable = Observable<Int>.interval(.seconds(1), scheduler: scheduler)
    .take(5) // 5번만 방출
observable.subscribe(onNext: { data in
    print("onNext: \(data)")
}).disposed(by: disposeBag)

let input = readLine() // input이 들어오기 전까지 Xcode Command Line Tools가 종료되지 않도록 하기위해 선언

Traits (특수한 Observable)

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

1. Single

  • 특징: 하나의 값 또는 에러를 방출하고 완료한다.
  • 사용 예시:
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)
  • 사용 사례: 네트워크 요청처럼 단일 응답을 기대하는 경우에 적합하다.

2. Maybe

  • 특징: 값을 방출하거나, 에러를 발생시키거나, 완료 이벤트만 발생시킨다.
  • 사용 예시:
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(.success(100))
    observer(.success(200))
    return Disposables.create()
}

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

maybe4.subscribe(onSuccess: { data in
    print("onSuccess: \(data)")
}, onError: { error in
    print("onError: \(error)")
}, onCompleted: {
    print("onCompleted")
}).disposed(by: disposeBag)
  • 사용 사례: 선택적으로 값을 반환해야 하는 경우 사용한다.

3. Completable

  • 특징: 값을 방출하지 않고 완료 여부만 알린다.
  • 사용 예시:
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)
  • 사용 사례: 작업 성공 여부만 확인하고자 할 때 사용한다.

Operation

Rx에서 제공하는 연산자로 Observable을 변환하거나 결합하며, 데이터 흐름을 제어하는 데 도움을 준다.

1. map

  • 특징: Observable의 값을 변환한다.
  • 사용 예시:
let observable = Observable<Int>.create { observer in
    observer.onNext(1)
    observer.onNext(2)
    observer.onNext(3)
    return Disposables.create()
}

observable
    .map { $0 * 10 }
    .subscribe(onNext: {
        print("onNext: \($0)")
    }).disposed(by: disposeBag)
  • 사용 사례: 숫자를 두 배로 늘리거나 문자열을 조작하는 작업 같은 데이터 변환이 필요한 경우 사용한다.

2. zip

  • 특징: 두 개의 Observable을 결합하여 쌍으로 묶는다.
  • 사용 예시:
let observableA = Observable<Int>.create { observer in
    observer.onNext(1)
    observer.onNext(2)
    observer.onNext(3)
    observer.onNext(4)
    return Disposables.create()
}

let observableB = Observable<String>.create { observer in
    observer.onNext("A")
    observer.onNext("B")
    observer.onNext("C")
    return Disposables.create()
}

Observable.zip(
    observableA,
    observableB
)
.subscribe(onNext: { data in // data는 (int, String)의 튜플
    print("onNext: \(data.0), \(data.1)")
}).disposed(by: disposeBag)
  • 사용 사례: 두 데이터 스트림을 결합해야 할 때 사용한다.

3. merge

  • 특징: 두 개의 Observable을 하나의 스트림으로 합친다.
  • 사용 예시:
let observableA = Observable<Int>.interval(.seconds(2), scheduler: SerialDispatchQueueScheduler(qos: .default))
    .take(3)
    .map { "A -> \($0)번째 방출" }

let observableB = Observable<Int>.interval(.seconds(5), scheduler: SerialDispatchQueueScheduler(qos: .default))
    .take(3)
    .map { "B -> \($0)번째 방출" }
Observable.merge(
    observableA,
    observableB
)
.subscribe(onNext: { value in
print("onNext: \(value)")
}).disposed(by: disposeBag)

let input = readLine()
  • 사용 사례: 병렬적으로 발생하는 이벤트를 처리할 때 사용한다.

4. flatMap

  • 특징: Observable의 방출값을 다른 Observable로 변환한다.
  • 사용 예시:
let observable = Observable<Int>.interval(.seconds(2), scheduler: SerialDispatchQueueScheduler(qos: .default))
    .take(5)

let fruitDictonary = [
    0: "사과",
    1: "바나나",
    2: "오렌지",
    3: "멜론"
]

func getFruitObservable(_ number: Int) -> Observable<String> {
    return Observable.create { observer in
        guard let fruit = fruitDictonary[number] else {
            observer.onError(MyError())
            return Disposables.create()
        }
        observer.onNext(fruit)
        return Disposables.create()
    }
}

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

let input = readLine()
  • 사용 사례: 값 변환 후 비동기 작업을 연결할 때 사용한다.

그 외 Operation

강의에서 다룬 Operation은 위와 같고, 추가로 concat, combineLatest, withLastestFrom, share, filter 등도 공부해보기로 하였다.

1. combineLatest

  • 특징:
    • 지정된 여러 Observable의 가장 최근 값을 결합하여 새로운 값을 방출한다.
    • 각 Observable이 처음 값을 방출하기 전에는 결합 결과가 생성되지 않으며, 그 이후에는 어떤 Observable이든 값을 방출할 때마다 최신 조합으로 결과를 업데이트한다.
  • 사용 예시:
let observableA = Observable.of(1, 2, 3)
let observableB = Observable.of("A", "B", "C")

Observable.combineLatest(observableA, observableB) { a, b in
    return "\(a) and \(b)"
}
.subscribe(onNext: { data in
    print("onNext: \(data)")
}).disposed(by: disposeBag)
  • 사용 사례: 사용자의 여러 입력 필드(예: 이메일, 비밀번호)를 실시간으로 검증할 때 같이 최신 상태를 기준으로 이벤트를 처리할 때 유용하다.

2. filter

  • 특징:
    • 조건을 만족하지 않는 값을 걸러내어 불필요한 데이터를 방출하지 않도록 한다.
    • 지정된 조건이 true를 반환하는 경우에만 값을 방출한다.
  • 사용 예시:
let observable = Observable.of(1, 2, 3, 4, 5)

observable.filter { $0 % 2 == 0 }
    .subscribe(onNext: { data in
        print("onNext: \(data)")
    }).disposed(by: disposeBag)
  • 사용 사례: 사용자 입력 이벤트 중 특정 조건(예: 입력 길이, 특정 키워드 포함)을 만족하는 값만 처리할 때 유용하다.

3. concat

  • 특징:
    • 여러 하나의 Observable이 완료된 이후에 다음 Observable을 시작하며, 순차적으로 모든 값을 방출한다.
    • 각각의 Observable은 순서대로 구독되고 완료된다.
  • 사용 예시:
let observableA = Observable.of(1, 2, 3)
let observableB = Observable.of(4, 5, 6)

Observable.concat(observableA, observableB)
    .subscribe(onNext: { data in
        print("onNext: \(data)")
    }).disposed(by: disposeBag)
  • 사용 사례: Observable을 순서대로 처리해야 하는 경우 사용한다.

4. withLatestFrom

  • 특징:
    • 트리거 트리거 Observable이 새로운 값을 방출할 때, 다른 Observable의 가장 최근 값을 결합하여 결과를 방출한다.
    • 트리거 Observable이 동작할 때만 반응하므로, 특정 이벤트를 기준으로 최신 데이터를 사용하고자 할 때 적합하다.
  • 사용 예시:
let trigger = PublishSubject<Void>()
let data = BehaviorSubject(value: "Initial")

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

data.onNext("Updated")
trigger.onNext(())
  • 사용 사례: 버튼 클릭 시 사용자가 현재 선택한 값을 기반으로 작업을 수행해야 하는 시나리오 같이 특정 이벤트 시 최신 상태를 기반으로 동작을 수행할 때 사용한다.

5. share

  • 특징:
    • 원본 Observable을 여러 구독자 간에 공유하는 역할을 한다.
    • 기본적으로 Cold Observable을 Hot Observable처럼 동작하게 만들어, 동일한 소스 데이터 스트림을 효율적으로 사용할 수 있다.
    • 리소스 소모가 큰 작업(네트워크 호출, 데이터 처리 등)에서 중복 실행을 방지한다.
  • 사용 예시:
let observable = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
    .take(5)
    .share()

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

observable.subscribe(onNext: { data in
    print("Subscriber 2: \(data)")
}).disposed(by: disposeBag)
  • 사용 사례: 동일한 Observable을 여러 구독자와 효율적으로 공유하고자 할 때 사용한다. 실시간 데이터 스트림(예: 타이머, 센서 데이터)에서 동일한 결과를 여러 화면이나 컴포넌트가 사용해야 할 때 적합하다.

0개의 댓글