Post

[Day 49] Reactive Programming

RxSwift를 사용하여 Reactive Rrogramming하기! Part.3

[Day 49] Reactive Programming

📌 Scheduler

SchedulerObsevable 연산자들이 동작하는 실행 Context(Thread)정의하는 객체에요.

Rx에서의 연산은 기본적으로 동기적이고, 현재 스레드에서 실행되지만, subscribe(on:)obseve(on:)을 사용하면 명시적으로 실행 Thread를 제어가 가능해요

예시를 한번 볼게요

1
2
3
4
5
6
7
8
9
10
let mainScheduler = MainScheduler.instance
let backgroundScheduler = ConcurrentDispatchQueueScheduler(qos: .background)

Observable<Int>.just(100)
  .subscribe(on: backgroundScheduler) // Observable 시작은 백그라운드에서
  .map { $0 * 10 }                    // 여전히 백그라운드에서 실행
  .observe(on: mainScheduler)         // 이 시점부터 메인 스레드로 이동
  .filter { $0.isMultiple(of: 20) }   // 메인 스레드에서 실행
  .subscribe(onNext: { print($0) })   // 메인 스레드에서 결과 처리
  .disposed(by: disposeBag)
  • subscribe(on:) → 무거운 작업(예: 네트워크 요청, 파일 읽기 등)은 백그라운드에서 처리하고
  • observe(on:) → UI 작업은 무조건 메인 스레드에서 해야 하니까 그 전에 바꿔주는 거에요.

subscribe(on:) 과 observe(on:)의 차이

| | subscribe(on:) | observe(on:) | | — | — | — | | 목적 | Observable이 생성되고 실행되는 Thread를 결정 | 이벤트가 처리되는 Thread를 지정 | | 시점 | Observable Chain 가장 처음 영향 | 호출 이후 모든 Chain 영향 | | 예시 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) | .observeOn(MainScheduler.instance) |

주요 Scheduler 종류

| Scheduler | 설명 | 주로 쓰는 용도 | | — | — | — | | MainScheduler.instance | 메인 스레드(UI Thread) | UI 업데이트 | | ConcurrentDispatchQueueScheduler | GCD의 백그라운드 큐 | 네트워크 요청, 디스크 작업 등 | | SerialDispatchQueueScheduler | GCD의 직렬 큐 | 순서 보장해야 하는 작업 | | OperationQueueScheduler | OperationQueue 기반 | 복잡한 백그라운드 작업 | | ImmediateSchedulerType | 지연 없이 즉시 실행 | 테스트용 혹은 단순 연산 | | CurrentThreadScheduler | 현재 스레드에서 실행 | 지연 없이 현재 흐름에서 처리 |

📌 Traits

Traits는 특정한 제약과 의미를 부여한 Observable의 추상화된 형태에요 모든 Traits는 asObservable() 연산을 통해 Observable로 변환이 가능하다!

먼저 간단하게 Traits에 대해 뭐가 있는지 살펴 보자

Traits설명용도이벤트
Single하나의 ElementData가 1회만 발생할 때
(네트워크 API, DB 쿼리)
success(Element)
failure(Error)
CompletableCompleted 또는 Error 방출Data 없이 완료 여부만 필요할 때completed
error(Error)
Maybe하나의 Element 또는 Completed, Error 방출Optional한 데이터 흐름success(Element)
completed
error(Error)
Infallible하나의 Element 또는 Completed 방출Error가 없는 이벤트 흐름next(Element)
completed

Single

1
2
3
4
5
6
7
8
9
10
11
Single<UIImage>.create { single in
  downloadImage { result in
    switch result {
    case .success(let image): // success(Element)
      single(.success(image))
    case .failure(let error): // failure(Error)
      single(.failure(error))
    }
  }
  return Disposables.create()
}
  • success(Element)failure(Error)중 하나만 발생!

Complettable

1
2
3
4
5
6
Completable.create { completable in
  saveToDatabase { success in
    success ? completable(.completed) : completable(.error(MyError.db)) // completed or error(Error)
  }
  return Disposables.create()
}
  • 데이터는 모르겠고, 성공했는지, 실패했는지만 알고 싶을떄!

Maybe

1
2
3
4
5
6
7
8
Maybe<String>.create { maybe in
  if isValidUser {
    maybe(.success("UserName"))
  } else {
    maybe(.completed) // 아무 값도 없이 종료
  }
  return Disposables.create()
}
  • success, completed, error 3가지 중 하나 발생

    Infallible

1
2
3
4
5
6
7
8
9
func loadUserSettings() -> Infallible<Settings> {
  return Infallible.create { infallible in
    loadSettings {
      infallible(.next($0))
      infallible(.completed)
    }
    return Disposables.create()
  }
}
  • error가 절대 없는 상황에서 사용!
    • 값만 전달하고 성공/완료로 끝나는 단순한 스트림
    • 에러 처리를 생략하고 싶은 UI 데이터 바인딩

📌 Subject and Relay

SubjectRelay는 RxSwift와 RxCocoa에서 핵심적으로 사용되는 Hot Obsevable이에요.

SubjectRelayObservable이면서 Observer의 역할 을 한답니다.

 Subject (RxSwift)Relay (RxRelay)
오류 방출
스트림 완료
종류PublicshSubject
BehaviorSubject
ReplaySubject
AsyncSubJect
PublishRelay
BehaviorRelay

각각의 예시 코드와 정의를 알아볼까요??

PublicshSubject

구독 이후에 발생되는 이벤트만 전달하며, 이전 이벤트는 기억하지 않아요.

1
2
3
4
5
6
7
8
9
10
let publishSubject = PublishSubject<Int>()
publishSubject
  .subscribe(onNext: {
    print($0)
  })
  .disposed(by: disposeBag)
publishSubject.on(.next(1))
publishSubject.on(.next(2))
publishSubject.on(.next(3))
publishSubject.on(.completed)

일반적인 이벤트 처리나 단발성 알림에 적합해요

BehaviorSubject

반드시 초기값이 필요하며, 가장 최근의 이벤트 하나를 저장하고, 구독시 즉시 전달합니다.

1
2
3
4
5
6
7
8
9
10
let behaviorSubject = BahaviorSubject(value: 0)
behaviorSubject
  .subscribe(onnext: {
    print($0)
  })
  .disposed(by: disposeBag)
behaviorSubject.on(.next(1))
behaviorSubject.on(.next(2))
behaviorSubject.on(.next(3))
behaviorSubject.on(.completed)

상태를 계속 추적하고 최신 상태를 공유할 때 적합해요

ReplaySubject

지정 된 버퍼 크기 만큼의 이전 이벤트를 저장하고, 구독시 버퍼에 저장된 모든 이벤트를 즉시 전달해요

1
2
3
4
5
6
7
8
9
10
let replySubject = ReplaySubject<Int>.create(bufferSize: 2)
replySubject.on(.next(1))
replySubject.on(.next(2))
replySubject
  .subscribe(onNext: {
    print($0)
  })
  .disposed(by: disposeBag)
replySubject.on(.next(3))
replySubject.on(.completed)

값의 히스토리를 유지해야 할 때 적합해요

AsyncSubJect

completed 이벤트 발생시, 가장 마지막 값을 전달해요

1
2
3
4
5
6
7
8
9
10
let asyncSubject = AsyncSubject<Int>()
asyncSubject
  .subscribe(onNext: {
    print($0)
  })
  .disposed(by: disposeBag)
asyncSubject.on(.next(1))
asyncSubject.on(.next(2))
asyncSubject.on(.next(3))
asyncSubject.on(.completed)

작업이 끝난 뒤 최종 결과만 필요할 경우 적합해요

PublishRelay

PublishSubject에서 에러와 완료 이벤트를 제거한 형태에요

1
2
3
4
5
6
7
8
9
let publishRelay = PublishRelay<Int>()
publishRelay
  .subscribe(onNext: {
    print($0)
  })
  .disposed(by: disposeBag)
publishRelay.accept(1)
publishRelay.accept(2)
publishRelay.accept(3)

UI이벤트 처리처럼 에러가 나면 안되는 상황에 적합해요

BehaviorRelay

BehaviorSubject에서 에러와 완료를 제거한 형태로, 현재 상태 값을 저장하고 구독시 전달해요

1
2
3
4
5
6
7
8
9
let behaviorRelay = BehaviorRelay(value: 0)
behaviorRelay
  .subscribe(onNext: {
    print($0)
  })
  .disposed(by: disposeBag)
behaviorRelay.accept(1)
behaviorRelay.accept(2)
behaviorRelay.accept(3)

마찬가지로 UI이벤트 처리처럼 에러가 나면 안되는 상황에 적합해요

📌 RxCocoa

RxSwift를 UIKit, AppKit등 Apple의 UI프레임워크에 통합하기 위한 확장 라이브러리에요

RxCocoa PNG

 BinderControlEventControlProperty
방향단방향(State → UI)단방향(UI → State)양뱡향
타입StateEventState + Evnet
목적UI 값 바인딩UI 이벤트 수신UI 값 바인딩
UI 이벤트 수신
예시모든 UI Property
@dynamicMemberLookup으로 자동 생성
UIButton.rx.tap
UIScrollerView.rx.didScroll
UICollectionView.rx.itemSeleted
UITextFiedld.rx.text
UISwitch.rx.isOn
UISlider.rx.value
UIDatePicker.rx.date

간단한 사용자 정의 Binder, ControlEvent, ControlProperty 를 만들어 볼까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyButton: UIControl {
  ..
}

extension Reactive where Base: MyButton {
  var tap: ControlEvent<Void> {
    controlEvent(.touchUpInside)
  }
}

class MyToggleSwitch: UIControl {
  var isOn: Bool = false
  ...
}

extension Reactive where Base: MyToggleSwitch {
    var isOn: ControlProperty<Bool> {
    controlProperty(editingEvents: .valueChanged) { toggleSwitch in
      toggleSwitch.isOn
    } setter: { toggleSwitch, isOn in
      toggleSwitch.isOn = isOn
    }
  }
}

맺으며: 춘식이와 함께하는 스마트한 개발

오늘도 오류 내며 자란 춘식이였습니다. 🐾 더 열심히 노력 해서 좋은 iOS Developer가 되자!

혹시 이 글을 보시는 분들 중 더 유용한 팁이 있다면 댓글로 많이 알려주세요!

This post is licensed under CC BY 4.0 by the author.