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 Driver
và Signal
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ả Driver
và Signal
đề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 Infailable
vì Infailable
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()
và 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 Driver
và Signal
:
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