RxSwift 시즌 2 정리

2025. 1. 12. 00:03

영상



Swift에서 비동기(async)로 작업하는 경우 completion을 통해서 값을 가져오고 이 값을 사용할 때마다 계속 함수를 호출해야 했음

// 함수 선언
func downloadJson(_ url: String, completion: @escaping ((String?) -> Void)){
    let url = URL(string: url)!
    DispatchQueue.global().async{
        let data = try! Data(contentsOf: url)
        let json = String(data: data, encoding: .utf8)
        completion(json)
    }
}
// 사용
downloadJson(MY_URL){ json in
    let result = json
}

 

 

이 어려움을 덜어주기 위해 등장한 것이 RxSwift

나중에 생기는 데이터 Observable로 선언하고 subscribe 후 Event(next / error / completed)가 발생

Observable을 생성 할 때는 create(), return 시에는 Disposables.create() 사용


 

위 방식과 다르게

1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴

func downloadJson(_ url: String) -> Observable<String?>{
	return Observable.create(){ emitter in
    	let url = URL(string: url)!
    	let task = URLSession.shared.dataTask(with: url){ data, response, error in
        	// onError
            guard error == nil else{
            	emitter.onError(error!)
                return
            }
            // onNext
            if let data = data, let json = String(data: data, encoding: .utf8){
            	emitter.onNext(json)
            }
            // completed
            emitter.onCompleted()
      	}
        // task resume
        task.resume()
        // task cancel
        return Disposables.create(){
        	task.cancel()
        }
    }
}

2. Observable로 오는 데이터를 받아서 처리

let observable = downloadJson(MEMBER_LIST_URL)
let disposable = observable.subscribe({ event in
    switch(event){
    case .next(let json):
        print(json)
        break
    case .error(let error):
    	print(error)
        break
    case .completed:
    	print("Completed")
        break
    }
})
disposable.dispose()

 

Observable의 생성 주기는 create() → subscribe → onNext 일단 작업은 끝나고

결과는 onCompleted / onError로 나오고

모든 동작이 종료되는 Disposed, Disposed된 이후에는 재사용은 불가능함

 

2번의 데이터를 받아서 처리하는 방식을 간소화 할 수 있음

_ = downloadJson(MY_URL)
    .subscribe(onNext: { print($0) },
               onError: { error in print(error) },
               onCompleted: { print("Completed") })

 

subscribe는 return 타입이 Disposable

Disposable 소멸 후에 Bag 에 담음 → DisposeBag


생성자를 통해 Observable 생성할 수 있음

  • just : 데이터 1개만 전송
  • from : 데이터 여러 개 전송

Operator는 observable과 subscribe 사이에 받아서 데이터를 중간에 바꿈

  • map, filter 등등
  • observeOn : observeOn 작성한 이후의 스케줄러 (DownStream)
  • subscribeOn : 맨 처음 시작부터 사용되는 스케줄러 → 순서 상관 없음 (UpStream)
    여기서 스케줄러는 Schedular 타입 → OperationQueue
  • scan(x, y) : x + y
 

ReactiveX - Operators

Introduction Each language-specific implementation of ReactiveX implements a set of operators. Although there is much overlap between implementations, there are also some operators that are only implemented in certain implementations. Also, each implementa

reactivex.io

    _ = downloadJson(MEMBER_LIST_URL)
        .map{ json in json?.count ?? 0 }    // String -> Int (operator)
        .filter{ cnt in cnt > 0}            // cnt > 0 (operator)
        .map{ "\($0)"}                      // Int -> String (operator)
        .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default))
        .observeOn(MainScheduler.instance)  // super : operator(DispatchQueue.main)
        .subscribe(onNext: { json in
            self.editView.text = json
            self.setVisibleWithAnimation(self.activityIndicator, false)
        })

 

Combining

  • zip : 두 데이터가 하나씩 생성되면 쌍으로 전달, 쌍으로 만들지 못하면 생성되지 않음
  • combineLastest : 가장 많이 사용함. zip과 유사한 기능. 쌍이 없어도 최근 것이랑 쌍을 만들어 내려감
  • merge : 데이터를 순서대로 두 개 합치는데 데이터 타입이 서로 같아야함
    let jsonObservalbe = downloadJson(MEMBER_LIST_URL)
    let helloObservable = Observable.just("Hello World")

    // Hello World과 json이 함께 출력
    Observable.zip(jsonObservalbe, helloObservable) { $1 + "\n" + $0 }

 

RxCoca = RxSwift + UIKit

subscribe를 bind로 편리하게 사용할 수 있음

// subscribe 사용
    viewModel.itemsCount
            .map{ "\($0)" }
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: {
                self.itemCountLabel.text = $0
            })
            .disposed(by: disposeBag)
// bind(to:) 사용
     viewModel.itemsCount
        .map{ "\($0)" }
        .observeOn(MainScheduler.instance)
        .bind(to: itemCountLabel.rx.text)
        .disposed(by: disposeBag)

 

Observable 값을 컨트롤 하기 위해 Subject를 사용함

Subject통제가 가능하면서도 Observable 기능(onNext/onError)도 함

observable 밖에서 데이터를 컨트롤 해서 새로운 값을 줄 수 있는 onNext로 하는 것을 한번에 Subject를 사용

  • PublishSubject : 데이터 생성되면 subscribe 하는 애들한테 그대로 보내줌
  • BehaviorSubject : 기본 값이 있고, 초기에 기본 값이 있다가 값이 바뀔 때 마다 내려줌
  • asyncSubject : complete전까지 값을 안보내다가 complete 후에 값을 전달
  • replaySubject : 처음 subscribe했을 때는 PublishSubject와 동작은 동일, 그 이후에 다시 subscribe하면 전에 있던 데이터 까지 모두 전달

Subject를 선언하고 RxCocoa로 사용하는 예제

// ViewModel에 선언
var menuObservable = BehaviorSubject<[Menu]>(value: [])

 

UITableViewDataSource 사용하지 않고 바로 viewDidLoad에서 bind

viewModel.menuObservable
    .observeOn(MainScheduler.instance)
    .bind(to: tableView.rx.items(cellIdentifier: cellId,
                                 cellType: MenuItemTableViewCell.self)){ _, item, cell in

        cell.title.text = item.name
        cell.price.text = "\(item.price)"
        cell.count.text = "\(item.count)"

        cell.onChnage = { [weak self] increase in
            self?.viewModel.changeCount(item: item, increase: increase)
        }

    }.disposed(by: disposeBag)

 

take : 실행 횟수

delay : 설정한 시간 뒤에 다음 작업 진행

.delay(.seconds(1), scheduler: MainScheduler.instance)
.take(1)

 

RxCocoa 사용 시에는 UI 작업은 Main Thread에서만 작업

observeOn(MainScheduler.instance) 무조건 추가해야함

 

에러 발생 시 catchErrorJustReturn 사용 하여 에러가 발생했을 때 예외처리

 

Main Thread에서 작업하고 예외처리를 동시에 할 수 있음 → Driver

    // 기존
    viewModel.itemsCount
        .map{ "\($0)" }
        .catchErrorJustReturn("")
        .observeOn(MainScheduler.instance)
        .bind(to: itemCountLabel.rx.text)
        .disposed(by: disposeBag)
    // Driver 사용
    viewModel.itemsCount
        .map{ "\($0)" }
        .asDriver(onErrorJustReturn: "")
        .drive(itemCountLabel.rx.text)
        .disposed(by: disposeBag)

 

Subject가 Stream이 끊어지면 안됨, 끊어지지 않는 Subject → RxRelay

complete / error 가 발생하지 않고 오로지 next만 발생하고, next가 아닌 accept 사용

var menuObservable = BehaviorRelay<[Menu]>(value: [])
// onNext 대신 accept 사용
menuObservable.accept($0)

 

RxCocoa에서 UI 작업에 특화되게 사용함

RxSwift RxCocoa
Observable Driver
Subject Relay

 

728x90

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

[RxSwift] TimeBasedOperator  (0) 2023.10.03
[RxSwift] Combine Operator  (0) 2023.10.03
[RxSwift] Traits  (0) 2023.09.18
[RxSwift] Subject  (0) 2023.09.16
[RxSwift] Observable  (0) 2023.09.10

BELATED ARTICLES

more