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

What's new in RxSwift6(1)

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을 구현해 주도록 하였습니다.

```@dynamicMemberLookup```

위 사진 처럼 RxSwift6에서는 따로 작업을 하지 않아도 바로 .rxBinder를 볼 수 있습니다! 👍

@dynamicMemberLookup은 Swift4.2에서 처음으로 소개되었습니다. classstruct에서 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을 구현해 줍니다. 이렇게 DispoaseBagbuilder block을 적용할 수 있는 struct@_functionBuilder 이용해 만들어 init 적용해 주면, 간단하게 Builder 패턴을 적용할 수 있습니다.


Single의 구현을 Swift의 Result를 사용하도록 변경

RxSwift에서 Single객체elementserror만 방출하는 Observable 객체입니다.

RxSwift5에서는 Single객체를 구현하기 위해 elementserror를 표현할 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은 successfailure 케이스가 있는 enum으로 구현되었습니다. 이는 위의 SingleEvent와 똑같은 형태이기 때문에 RxSwift6에서는 SingleEventResulttypealias 하여 사용하게 되었습니다.

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에서 elementKeyPath를 이용하여 property에 접근하도록 구현 되었습니다.

1
2
3
public func distinctUntilChanged<Property: Equatable>(at keyPath: KeyPath<Element, Property>) -> Observable<Element> {
    self.distinctUntilChanged { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
}
This post is licensed under CC BY 4.0 by the author.