[iOS 10주차] RxSwift 3부: Scheduler, Subject, Relay, RxCocoa

DoyleHWorks·2024년 12월 27일
1

Scheduler

Scheduler는 RxSwift에서 작업이 실행되는 스레드나 큐를 관리하는 역할을 한다.

Observable 구독을 통해서 UI 작업을 처리하게 되었다면, 그 작업이 메인 스레드에서 동작하도록 명시하는 것이 좋다.
( “UI 작업은 메인 스레드에서 이루어져아한다” → iOS 앱 개발의 기본 개념 )

Scheduler의 종류

MainScheduler

  • 설명: 메인 스레드에서 작업을 실행하기 위한 Scheduler.
  • 사용 예: UI 업데이트와 같이 메인 스레드에서 실행해야 하는 작업.
  • 예제:
    Observable.just("Hello")
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: { value in
            print(value) // UI 업데이트
        })

ConcurrentDispatchQueueScheduler

  • 설명: GCD를 사용해 병렬 작업을 실행하는 Scheduler.
  • 사용 예: 백그라운드에서 실행해야 하는 계산 작업.
  • 예제:
    let scheduler = ConcurrentDispatchQueueScheduler(qos: .background)
    Observable.just("Background Task")
        .subscribeOn(scheduler)
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: { value in
            print(value)
        })

참고: Quality of Service

  • userInteractive (최고 우선순위 - e.g. UI 업데이트 ,애니메이션)
  • userInitiated (높은 우선순위 - e.g. 버튼 클릭 후 데이터 로딩)
  • default (기본 우선순위 - e.g. 일반적인 백그라운드 작업)
  • utility (낮은 우선순위 - e.g. 데이터 다운로드, 백업)
  • background (최저 우선순위 - e.g. 대용량 데이터 정리, 동기화)
  • unspecified (시스템 결정 - 어떤 우선순위로 처리해도 상관없는 작업)

SerialDispatchQueueScheduler

  • 설명: GCD를 사용해 순차적으로 작업을 실행하는 Scheduler.
  • 사용 예: 특정 작업의 순서를 보장해야 하는 경우.
  • 예제:
    let queue = DispatchQueue(label: "com.example.serial")
    let scheduler = SerialDispatchQueueScheduler(queue: queue, internalSerialQueueName: "serialQueue")
    Observable.of(1, 2, 3)
        .subscribeOn(scheduler)
        .subscribe(onNext: { value in
            print(value)
        })

subscribeOn & observeOn

  • subscribeOn: Observable이 구독될 때 사용할 스레드를 지정한다.
    • 스트림이 시작되는 곳의 스레드를 지정
    • Observable의 생성과 초기화
  • observeOn: Observable이 방출하는 이벤트를 처리할 스레드를 지정한다.
    • 다운스트림의 스레드를 지정
    • 데이터 처리 및 UI 업데이트
https://reactivex.io/documentation/scheduler.html

예제:

Observable.of(1, 2, 3)
    .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) // Observable 생성 시 백그라운드 스레드 사용
    .observeOn(MainScheduler.instance) // UI 업데이트는 메인 스레드에서 실행
    .subscribe(onNext: { value in
        print(value)
    })

Subject

Subject는 Observable과 Observer의 역할을 동시에 하는 특별한 타입이다.

BehaviorSubject

https://reactivex.io/documentation/subject.html
  • 설명: 최신 값을 유지하며, 새로운 구독자에게 최신 값을 방출.
  • 초기값이 필요.
  • 사용 예: 앱 상태 관리 또는 데이터 캐싱.
  • 예제:
    let subject = BehaviorSubject(value: "Initial Value")
    subject.onNext("Second Value")
    
    subject.subscribe(onNext: { value in
        print(value)
    })

PublishSubject

https://reactivex.io/documentation/subject.html
  • 설명: 구독 이후에 방출된 이벤트만 구독자에게 전달.
  • 초기값이 필요하지 않음.
  • 사용 예: 특정 이벤트 스트림 전달.
  • 예제:
    let subject = PublishSubject<String>()
    subject.onNext("Ignored")
    
    subject.subscribe(onNext: { value in
        print(value)
    })
    
    subject.onNext("Emitted")

Relay

Relay는 RxCocoa에서 제공하며, 에러나 완료를 방출하지 않고 UI 중심 작업에서 주로 사용된다.

BehaviorRelay

  • 설명: BehaviorSubject의 Wrapper. 최신 값을 유지하며, 초기값이 필요.
  • 차이점: .accept() 메서드로 값을 방출, 에러 방출 불가.
  • 예제:
    let relay = BehaviorRelay(value: "Initial Value")
    relay.accept("Updated Value")
    
    relay.asObservable()
        .subscribe(onNext: { value in
            print(value)
        })

PublishRelay

  • 설명: PublishSubject의 Wrapper. 초기값이 필요하지 않으며, 에러 방출 불가.
  • 예제:
    let relay = PublishRelay<String>()
    
    relay.accept("Event 1")
    
    relay.asObservable()
        .subscribe(onNext: { value in
            print(value)
        })
    
    relay.accept("Event 2")

RxCocoa

  • 설명: RxSwift를 사용해 UIKit 및 Cocoa의 인터페이스를 Reactive Programming 방식으로 확장한 라이브러리.
  • 주요 기능:
    • UIKit 요소 (UIButton, UILabel 등)에서 Observable 생성.
    • Bindings를 통해 MVVM 패턴 지원.
  • 예제:
    let button = UIButton()
    button.rx.tap
        .subscribe(onNext: {
            print("Button tapped")
        })
        .disposed(by: disposeBag)
    
    let textField = UITextField()
    textField.rx.text.orEmpty
        .bind(to: viewModel.inputText)
        .disposed(by: disposeBag)

RxCocoa 활용 예제

Rx를 활용해서 버튼을 클릭했을 때 랜덤한 정수를 생성하고,
생성된 정수가 짝수면 버튼의 색상이 초록색,
생성된 정수가 홀수면 버튼의 색상이 빨간색으로 바뀌는 로직을 작성하고 싶어.
이때 버튼의 중복 클릭이나 과도한 연속 클릭을 방지하기 위해 throttle도 적용할거야.

import UIKit
import SnapKit
import RxSwift
import RxCocoa

struct MyError: Error {}

let ioScheduler = SerialDispatchQueueScheduler(qos: .default)

class ViewController: UIViewController {

    let disposeBag = DisposeBag()
    
    let button: UIButton = {
        let button = UIButton()
        button.backgroundColor = .blue
        button.setTitle("버튼", for: .normal)
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        initUI()
        bind()
    }

    func initUI() {
        view.backgroundColor = .white
        [button].forEach { view.addSubview($0) }
        
        button.snp.makeConstraints {
            $0.width.equalTo(120)
            $0.height.equalTo(80)
            $0.center.equalToSuperview()
        }
    }
    
    func bind() {
        button.rx.tap
            .throttle(.seconds(2), scheduler: MainScheduler.instance)
            .map { [weak self] in
                guard let self else { throw MyError() }
                return getRandomInt().isEvenNumber()
            }
            .subscribe(on: ioScheduler)
            .observe(on: MainScheduler.instance)
            .subscribe(onNext: { [weak self] isEvenNumber in
                guard let self else { return }
                
                if isEvenNumber {
                    button.backgroundColor = .green
                } else {
                    button.backgroundColor = .red
                }
            }).disposed(by: disposeBag)
    }
    
    
    /// 1부터 10 중에 랜덤한 정수를 얻는 함수.
    ///
    /// - Returns: 랜덤 정수.
    func getRandomInt() -> Int {
        let randomNumber = Int.random(in: 1...10)
        print("랜덤 정수: \(randomNumber)")
        return randomNumber
    }
}

extension Int {
    
    /// 파라미터로 들어온 정수가 짝수인지 판별하는 함수,
    ///
    /// - Parameter number: 판별할 정수.
    /// - Returns: 짝수면 true, 홀수면 false.
    func isEvenNumber() -> Bool {
        self % 2 == 0
    }
}

profile
Reciprocity lies in knowing enough

0개의 댓글