Posts What's new in RxSwift6(2)
Post
Cancel

What's new in RxSwift6(2)

앞 포스팅에서는 Swift5가 RxSwift6에 어떻게 적용되었는지 살펴보았습니다. 이번 포스팅에서는 RxSwift6에 새롭게 추가된 Observable typeOperation을 알아보고, 사용성을 어떻게 개션 되었는지 살펴 보도록 하겠습니다! 👀

Infallible

Infallible은 RxSwift6에서 새롭게 추가된 Observable type입니다. Observable과 다르게 .error를 방출(emit)하지 않습니다. 이는 RxCocoaRelay와 비슷한 개념(relay에서는 .completed.error를 방출 하지 않습니다.)으로 .error로 인해 subscribe가 종료되는 것을 방지할때 유용하게 쓰일거 같습니다.

1
2
3
4
5
6
7
8
9
10
Infallible<String>.create { observer in
    observer(.next("Hello"))
    observer(.next("World"))
    observer(.completed)
    // 여기에서 .error를 방출할 수 없습니다.

    return Disposables.create {
        // Clean-up
    }
}

물론 비슷한 기능을 하는 DriverSignal이 있습니다. 하지만 이 둘은 RxCocoa에 UI작업을 위해 Observable 이용해 MainScheduler에서 작업이 되도록 구현되어 있습니다. 하지만 Infallible은 완전히 새로운 Observable type 이라는 점에서 차이가 있습니다! 🆕


ReplayRelay 연산자 추가

RxSwift6에서는 ReplayRelayPublishRelay에서 ReplayRelay를 추가하여 3가지의 Relay를 제공합니다. Relay에서는 .completed.error를 방출 하지 않아, .error.completed로 인한 subscribe가 종료되는 것을 방지 할 수 있습니다. 사용법은 ReplaySubject 와 동일합니다.

1
2
3
4
5
// Subject
ReplaySubject<Int>.create(bufferSize: 3)

// Relay
ReplayRelay<Int>.create(bufferSize: 3)


withUnretained 추가 🌟

RxSwift로 구현된 ViewModelstreamViewBinding하기 위해서는 self에 대한 참고를 capture lists를 통해 받습니다. 이와 같은 작업은 매번 Binding을 할때마다 반복적으로 작업을 해줘야 했습니다.

1
2
3
4
5
6
viewModel.importantInfo
    .subscribe(onNext: { [weak self] info in 
        guard let self = self else { return }
        self.doImportantTask(with: info)
    })
    .disposed(on: disposeBag)

이러한 반복을 멈추도록 withUnretained operation 이 추가 되었습니다!👍 withUnretained operation을 통해 미리 self를 참조 받아 capture lists 없지 작업을 할 수 있습니다.

1
2
3
4
5
6
viewModel.importantInfo
  .withUnretained(self) // (Object, Element) 듀플 반환
  .subscribe(onNext: { owner, info in 
    owner.doImportantTask(with: info)
  })
  .disposed(by: disposeBag)

이제는 많은 예제의 bindViewmodel() 함수에 output에 미리 self 참조를 받아서 사용할 것 같네요.

1
2
3
4
5
6
7
func bindViewModel() {
    ...
    let output = viewModel.output.withUnretained(self)

    // view와 bind 작업
    ...
}


**Observable를 위한 decode(type:decoder:)연산자 제공**

Swift의 Codable을 이용하여 data decode를 하는 Operationdecode(type:decoder:) 추가 되었습니다.

1
2
3
service.rx
    .fetchJSONUsers() // Observable<Data> 반환
    .decode(type: [User].self, decoder: JSONDecoder()) // Observable<[User]> 반환

해당 Operation의 추가로 Rx를 이용한 네트워크 라이브러들의 decode operation이 변경될꺼 같습니다. 예로 RxMoya는 아래와 같이 decode 함수를 작성했습니다.(In History)

1
2
3
4
/// Maps received data at key path into a Decodable object. If the conversion fails, the signal errors.
func map<D: Decodable>(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) -> Single<D> {
    return flatMap { .just(try $0.map(type, atKeyPath: keyPath, using: decoder, failsOnEmptyData: failsOnEmptyData)) }
}


driver(), emit()에 다중 바인딩 제공

RxSwift6에서 DriverSignal에서도 multi Binding을 지원합니다. 이로인해 RxSwif5에서는 각각의 view에 subscribe를 해주던 작업을 한번에 할 수 있게 되었습니다.

1
2
3
4
5
6
7
8
9
10
11
// RxSwift5
viewModel.string.drive(input1)
viewModel.string.drive(input2)
viewModel.string.drive(input3)

viewModel.number.emit(input4)
viewModel.number.emit(input5)

// RxSwift6
viewModel.string.drive(input1, input2, input3)
viewModel.number.emit(input4, input5)

이는 Swift에 Variadic Parameters를 사용하여 구현되었습니다. Variadic Parameters는 파라미터로 특정 객체를 한개 이상 받을 수 있도록 합니다.

RxSwift5에서는 Drive 한개의 Observer 객체만 받아서 사용하고 있습니다.

1
2
3
4
5
// RxSwift5
public func drive<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {  
    MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage)
    return self.asSharedSequence().asObservable().subscribe(observer)
}

RxSwift6에서는 (1) Variadic Parameters 이용하여 한개 이상의 Observer 객체만 받아서 (2) 각각의 Observerelement를 넘겨주도록 구현되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
// RxSwift5
// (1) Variadic Parameters를 사용하여 한개 이상의 Observer을 받음
public func drive<Observer: ObserverType>(_ observers: Observer...) -> Disposable where Observer.Element == Element {
    MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage)
    return self.asSharedSequence()
                    .asObservable()
                    .map { $0 as Element? }
                    .subscribe { e in
                        // (2) 각각의 Observer에 element 넘겨줌
                        observers.forEach { $0.on(e) }
                    }
}


Binder가 RxCocoa에서 RxSwift로 이동

Binderinput, output을 간단히 Binding하기 위해 사용됩니다. 만약 Binder가 없다면 outputsubscribe받도록 설정해 주어야 합니다.

1
2
3
4
5
6
7
8
9
10
11
// RxSwift5
// not use Binder
viewModel.isButtonEnable
    .subscribe(onNext: { [weak self] in self?.myButton.isEnabled = $0 })
    .disposed(by: disposeBag)

// RxSwift6
// use Binder
viewModel.isButtonEnable
    .bind(to: myButton.rx.isEnabled)
    .disposed(by: disposeBag)

하지만 이번 RxSwift6에서는 RxCocoa에 있는 Binding이 넘어오면서, 간단한 bind를 사용하기 위해 RxCocoa를 사용할 필요가 없어졌습니다. 🙌

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