2021년 RxSwift6가 새로운 로고와 함께 릴리즈 됐습니다. 🚀🚀
변경사항에 대해서는 해당 포스팅에서 살펴 볼 수 있습니다. 이번 포스팅에서는 각각의 변경사항이 어떻게 구현됐는지 알아보고, 어떻게 쓰일지 정리해 보도록 하겠습니다!
Swift5
RxSwift6에서는 Swift5에서 새롭게 업데이트 된 내용이 반영되었습니다. 대표적으로 KeyPayh
와 @dynamicMemberLookup
를 이용한 property 접근으로 방식과 @functionBuilder
가 도입되었고, Result
type이 추가 되었습니다. 이번 장에서는 RxSwift6에 어떻게 Swift5를 사용하고 있는지 봐보도록 하겠습니다.
dynamicMemberLookup을 사용한 Binder의 자동 합성 🌟
이번 RxSwift6에서 하이라이트라고 생각되는 변경사항입니다!
RxSwift5에서는 특정 객체 프로퍼티에 .rx
를 이용하여 Binding을 하여 사용하기 위해서는, Reactive extension 이용하여 구현해 주어야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class MyView: UIView {
var title: String
var subtitle: String?
var icon: UIImage?
}
// implement Reactive extension
extension Reactive where Base: MyView {
var title: Binder<String> {
Binder(base) { base, title in
base.title = title
}
}
var subtitle: Binder<String?> {
Binder(base) { base, subtitle in
base.subtitle = subtitle
}
}
var icon: Binder<UIImage?> {
Binder(base) { base, icon in
base.icon = icon
}
}
// bind view
viewModel.title.bind(to: myView.rx.title)
viewModel.subtitle.bind(to: myView.rx.subtitle)
viewModel.icon.drive(myView.rx.icon)
이처럼 view를 만들때마다 Reactive extension을 구현해주는 일은 매우 비효율적이였습니다. 하지만 RxSwift6에서는 Swift5의 @dynamicMemberLookup
를 사용하여 자동으로 Reactive extension을 구현해 주도록 하였습니다.
위 사진 처럼 RxSwift6에서는 따로 작업을 하지 않아도 바로 .rx
로 Binder를 볼 수 있습니다! 👍
@dynamicMemberLookup
은 Swift4.2에서 처음으로 소개되었습니다. class나 struct에서 property를 존재 여부와 상관없이 subscript
로 접근가능 하도록 구현할 수 있습니다. 하지만 Swift5.1에서는 KeyPath
를 이용해 property을 랩핑하여 접근가능 하도록 구현할 수 있습니다. (Key Path Member Lookup)
RxSwift5의 Reactive
를 보면 다음과 같이 Base
만 Generic으로 받고, 나머지는 extension으로 Binding을 구현하도록 되었습니다..
1
2
3
4
5
6
7
8
9
10
11
12
// RxSwift5
public struct Reactive<Base> {
/// Base object to extend.
public let base: Base
/// Creates extensions with base object.
///
/// - parameter base: Base object.
public init(_ base: Base) {
self.base = base
}
}
하지만 RxSwift6에서는 @dynamicMemberLookup
을 이용해 Reactive
를 구현하여 따로 extension으로 작업하지 않더라도, KeyPath
를 이용하여 view의 property에 Binding을 할 수 있도록 구현 되었습니다. 🤝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// RxSwift6
@dynamicMemberLookup
public struct Reactive<Base: AnyObject> {
/// Base object to extend.
public let base: Base
/// Creates extensions with base object.
///
/// - parameter base: Base object.
public init(_ base: Base) {
self.base = base
}
public subscript<Property>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Property>) -> AnyObserver<Property> {
return AnyObserver { [weak base] event in
guard let base = base,
case .next(let value) = event else { return }
base[keyPath: keyPath] = value
}
}
}
이를 이용하여 RxSwift6에서는 Reactive extension을 이용해 각각의 view에서 구현해줘야 할 반복적인 작업을 KeyPath
를 이용하여 자동으로 처리할 수 있도록 구현 할 수 있었습니다.
DisposeBag 함수 빌더 추가 🏗
RxSwift6에세는 DisposableBuilder
를 구현하여 Builder 패턴을 사용할 수 있도록 하였습니다. 이로 인해 RxSwift5에서는 각각의 bind마지막에 DisposeBag
을 지정해주던걸 DisposableBuilder
를 이용해 표현할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// RxSwift5
let disposeBag = DisposeBag()
observable1.bind(to: input1).disposed(by: disposeBag)
observable2.drive(input2).disposed(by: disposeBag)
observable3
.subscribe(onNext: { val in
print("Got \(val)")
})
.disposed(by: disposeBag)
// RxSwift6
var disposeBag = DisposeBag {
observable1.bind(to: input1)
observable2.drive(input2)
observable3.subscribe(onNext: { val in
print("Got \(val)")
})
}
Builder 패턴을 사용하면 반복적으로 사용되는(위 코드에서는 .disposed(by: disposeBag)) 코드를 줄일 수 있고, disposeBag
이 사용되는 코드를 모을 수 있습니다.
RxSwift6에서는 Swift5.1에서 소개된 @_functionBuilder
를 사용하여 Builder 패턴를 구현했습니다. RxSwift6의 코드를 봐보겠습니다.
1
2
3
4
5
6
7
/// A function builder accepting a list of Disposables and returning them as an array.
@_functionBuilder
public struct DisposableBuilder {
public static func buildBlock(_ disposables: Disposable...) -> [Disposable] {
return disposables
}
}
우선 코드에서는 @_functionBuilder
를 이용하여 buildBlock
을 만들어 줄 수 있는 DisposableBuilder
을 구현해 줍니다.
1
2
3
4
5
6
7
extension DisposeBag {
/// Convenience init which utilizes a function builder to let you pass in a list of
/// disposables to make a DisposeBag of.
public convenience init(@DisposableBuilder builder: () -> [Disposable]) {
self.init(disposing: builder())
}
}
1
2
3
4
5
/// Convenience init which utilizes a function builder to let you pass in a list of
/// disposables to make a DisposeBag of.
public convenience init(@DisposableBuilder builder: () -> [Disposable]) {
self.init(disposing: builder())
}
그 후 DisposableBuilder
을 이용하여 DispoaseBag init
을 구현해 줍니다. 이렇게 DispoaseBag
에 builder block을 적용할 수 있는 struct을 @_functionBuilder
이용해 만들어 init
적용해 주면, 간단하게 Builder 패턴을 적용할 수 있습니다.
Single의 구현을 Swift의 Result를 사용하도록 변경
RxSwift에서 Single객체는 elements 와 error만 방출하는 Observable 객체입니다.
RxSwift5에서는 Single객체를 구현하기 위해 elements 와 error를 표현할 type이 필요했습니다. 이는 SingleEvent
라는 enum으로 구현되었습니다.
1
2
3
4
5
6
7
8
9
// RxSwift5
public enum SingleEvent<Element> {
/// One and only sequence element is produced. (underlying observable sequence emits: `.next(Element)`, `.completed`)
case success(Element)
/// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`)
case error(Swift.Error)
}
하지만 Swift5에서 Result
라는 새로운 type이 소개 되었습니다. Result type은 success 와 failure 케이스가 있는 enum으로 구현되었습니다. 이는 위의 SingleEvent
와 똑같은 형태이기 때문에 RxSwift6에서는 SingleEvent
를 Result
로 typealias
하여 사용하게 되었습니다.
1
2
3
// RxSwift6
public typealias SingleEvent<Element> = Result<Element, Swift.Error>
distinctUntilChange(at:)연산자에서 Key Path 지원
Swift5.2에서부터 Keypath type을 이용해 closure의 표현을 간단하게 할 수 있습니다.
RxSwift6에서도 distinctUntilChange(at:)
연산자에서 Keypath를 지원하여 간단하게 표현할 수 있습니다.
1
2
3
4
5
// RxSwift5
myStream.distinctUntilChanged { $0.searchTerm == $1.searchTerm }
// RxSwift6
myStream.distinctUntilChanged(at: \.searchTerm)
이는 RxSwfit6에서 element에 KeyPath
를 이용하여 property에 접근하도록 구현 되었습니다.
1
2
3
public func distinctUntilChanged<Property: Equatable>(at keyPath: KeyPath<Element, Property>) -> Observable<Element> {
self.distinctUntilChanged { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
}