Skip to content

Dashboard

Có gì mới ở RxSwift 6

Created by Admin

2020 là một năm đầy biến động toàn cầu. Sang năm 2021, ai ai cũng mong mọi thứ sẽ tốt đẹp hơn khi mà mọi người có thể trở lại cuộc sống bình thường như trước. Và để bắt đầu năm mới 2021, hãy chào đón phiên bản mới của RxSwift: RxSwift 6. Bài viết này sẽ giúp bạn nắm được một cách sơ lược những sự thay đổi ở phiên bản này.

Logo mới toanh

Không phải thay đổi gì trong code nhưng đây chắc chắn là một thứ đáng để đề cập tới.

RxSwift luôn sử dụng logo Volta Eel chính thống của Reactive Extensions. Nhưng ở phiên bản mới này, một logo mới đã được thêm vào để tạo sự riêng biệt cho RxSwift. Đây là sự kết hợp hoàn hảo giữa logo của ReactiveX và Swift.

Binder chuyển từ RxCocoa sang RxSwift

Đây là một sự thay đổi nhỏ nhưng rất quan trọng. Binder cho phép định nghĩa một cách để liên kết một Observable và phát cho đối tượng được gắn. Ví dụ:

viewModel.isButtonEnable.bind(to: myButton.rx.isEnabled)

Đây là cách mà chúng ta vẫn hay sử dụng Binder để liên kết tới một thuộc tính của View (ở đây là isEnable thông qua rx.isEnabled). Binder đã luôn ở trong RxCocoa nhưng nhận thấy được sự hữu dụng nên nó đã được chuyển sang RxSwift. Giờ đây để sử dụng Binder, ta không còn cần phải cài đặt RxCocoa mà chỉ cần mỗi RxSwift.

Tự động tổng hợp các Binder bằng @dynamicMemberLookup

RxSwift sử dụng thành phần .rx để ta có thể thêm các Reactive Extension cho một object. Như ví dụ sau ta thêm các thuộc tính cho MyView:

class MyView: UIView {
    var title: String
    var subTitle: String?
    var icon: UIImage?
}

Thông thường, ta sẽ thêm các Binder như này:

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
       }
    }
}

và sử dụng chúng:

viewModel.title.bind(to: myView.rx.title)
viewModel.subtitle.bind(to: myView.rx.subtitle)
viewModel.icon.drive(myView.rx.icon)

Việc này rất ổn, không có gì sai cả nhưng nó sẽ lặp đi lặp lại và thừa thãi. Tất cả những gì nó làm là tìm toàn bộ thuộc tính trong Base của Binder. Từ Swift 5.1, Apple đã bổ sung 1 tính năng vô cùng tiện lợi: @dynamicMemberLoopup

RxSwift 6 sẽ tự động sinh ra các Binder cho bất kỳ class nào. Việc này có nghĩa là đoạn code tạo Binder bên trên có thể bỏ đi rồi. (metghe)

Hãy sử dụng sự tân tiến của RxSwift 6 bằng cách viết .rx từ bất kỳ một class nào kế thừa từ AnyObject, ta sẽ thấy các Binder cho các thuộc tính trong object đang được gọi tới.

Cũng đừng lo lắng quá, các Reactive Extension mà các bạn viết sẽ được ưu tiên hơn so với những thuộc tính được sinh sẵn.

Operator mới: withUnretained

Một trong những đoạn code quen thuộc khi làm việc với RxSwift và iOS:

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

Đoạn này sẽ chỉ cho 1 output nhưng nó sẽ lặp đi lặp lại ở rất nhiều chỗ. Khá vướng mắt đấy. Giải quyết sao ta? (suynghi)

May mắn thay, RxSwiftExt có một operator rất hữu dụng trong trường hợp này: withUnretained. Bởi vì nó được sử dụng nhiều và rộng rãi nên cộng đồng đã mang nó vào RxSwift. Từ RxSwift 6, ta có thể viết lại đoạn code trên như sau:

viewModel.importantInfo
  .withUnretained(self) // Tuple of (Object, Element)
  .subscribe(onNext: { owner, info in 
    owner.doImportantTask(with: info)
  })
  .disposed(by: disposeBag)

Trông gọn gàng hơn nhiều ha

Infailable

Infailable là một kiểu luồng mới giống hệt với Observable với một sự khác biệt duy nhất - Nó không bao giờ lỗi. Điều này nghĩa là bạn không thể phát ra lỗi từ nó và được đảm bảo bởi compiler.

Để tạo một Infailable, ta làm giống như làm với Observable:

Infallible<String>.create { observer in
    observer(.next("Hello"))
    observer(.next("World"))
    observer(.completed)
    // No way to error here

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

Lưu ý rằng, ta chỉ có thể sử dụng .next(Element) hoặc .completed. Không có cách nào để luồng này bị lỗi. Tất cả các operator làm việc với Infailable cũng được đảm bảo như vậy (Bạn không thể gọi Infailable.error như Observable.error)

Nếu bạn đã làm việc với RxCocoa chắc chắn sẽ nghĩ ngay: "Cái đồ chơi mới mới này khác gì với DriverSignal vậy mấy ba?"

Đầu tiên, Infailable là một phần của RxSwift trong khi 2 thanh niên kia thuộc về RxCocoa. Nhưng quan trọng hơn, cả DriverSignal đều sử dụng MainScheduler và chia sẻ tài nguyên (sử dụng share()). Đây không phải tư tưởng của InfailableInfailable chỉ là một Observable đơn giản và được đảm bảo không lỗi trong compile-time.

Operator mới: decode(type:decoder:) cho Observable<Data>

RxSwift 6 thêm operator decode cho riêng cácObservable<Data> tương tự như Combine:

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

Nhiều tham số cho drive()emit()

RxSwift 5 đã giới thiệu nhiều tham số cho bind(to:):

viewModel.string.bind(to: input1, input2, input3)

Giờ RxSwift 6 sẽ mang điều tương tự tới cho DriverSignal:

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

Single giờ sử dụng Result của Swift

Cho tới RxSwift 5, Single có event riêng của nó:

public enum SingleEvent<Element> {
    case success(Element)
    case error(Swift.Error)
}

Ủa ủa, cái này giống hệt với Result<Element, Swift.Error> mà, sao phải thêm cái mới làm chi zậy? (boiroi)

Đúng rồi đó, từ RxSwift 6, SingleEvent chỉ đơn giản là một gán lại cho Result<Element, Swift.Error>. Việc này cũng làm thay đổi 1 số APIs, ví dụ như subscribe:

// RxSwift 5
single.subscribe(
    onSuccess: { value in
        print("Got a value: \(value)")
    },
    onError: { error in
        print("Something went wrong: \(error)")
    }
)

// RxSwift 6
single.subscribe(
    onSuccess: { value in
        print("Got a value: \(value)")
    },
    onFailure: { error in
        print("Something went wrong: \(error)")
    }
)

Thêm distinctUntilChanged(at:) cho Key Paths

distinctUntilChanged là một operator rất hữu dụng cho phép ta bỏ những phần tử giống nhau để tránh lãng phí tài nguyên xử lý những việc giống nhau.

myStream.distinctUntilChanged { $0.searchTerm == $1.searchTerm }

KeyPaths là một trong những điểm mạnh của Swift nên từ RxSwift 6, ta có thể viết lại như sau:

yStream.distinctUntilChanged(at: \.searchTerm)

Rất nhiều operator được đổi tên

Trong RxSwift 6, rất nhiều operator được đổi tên để tuân thủ code guideline của Swift nhiều nhất có thể:

RxSwift 5 RxSwift 6
catchError(_: ) catch(_: )
catchErrorJustReturn(_: ) catchAndReturn(_: )
elementAt(_: ) element(at:)
retryWhen(_: ) retry(when:)
takeUntil(_: ) take(until:)
takeUntil(behavior:_: ) take(until:behavior:)
takeWhile(_: ) take(while:)
takeWhile(behavior:_: ) take(while:behavior:)
take(.seconds(3)) take(for: .seconds(3))
skipWhile(_: ) skip(while:)
takeUntil(_: ) take(until:)
observeOn(_: ) observe(on:)
subscribeOn(_: ) subscribe(on:)

Kết luận

Còn rất nhiều những sự thay đổi khác trong RxSwift 6. Các bạn có thể tìm hiểu thêm ở trong changelog trên Github của RxSwift nhé. GLHF

Source: https://viblo.asia/p/co-gi-moi-o-rxswift-6-1Je5EQRY5nL