Let'Swift 2019 컨퍼런스에서 RxSwift to Combine 이라고 발표한 자료입니다. 자료 내에 언급되는 코드 링크는 다음과 같습니다.
[BringMyOwnBeer]
• RxSwift/RxCocoa: https://github.com/fimuxd/BringMyOwnBeer-
• Combine/SwiftUI: https://github.com/fimuxd/BringMyOwnBeer-Combine
8. Introduce
Combine declares publishers to
expose values that can change over
time, and subscribers to receive
those values from the publishers.
https://developer.apple.com/documentation/combine
9. Introduce
By adopting Combine, you’ll make your
code easier to read and maintain, by
centralizing your event-processing code
and eliminating troublesome techniques
like nested closures and convention-based
callbacks.
https://developer.apple.com/documentation/combine
40. Cancellable vs. Disposable
var disposeBag = DisposeBag()
Observable.just(1)
.subscribe(onNext: { number in
print(number)
})
.disposed(by: disposeBag)
var cancellables = Set<AnyCancellable>()
Just(1)
.sink { number in
print(number)
}
.store(in: &cancellables)
41. Cancellable vs. Disposable
var disposeBag = DisposeBag()
Observable.just(1)
.subscribe(onNext: { number in
print(number)
})
.disposed(by: disposeBag)
var cancellables = Set<AnyCancellable>()
Just(1)
.sink { number in
print(number)
}
.store(in: &cancellables)
70. class BeerListViewController: UIViewController {
let tableView = UITableView()
func bind(_ viewModel: BeerListViewBindable) {
self.disposeBag = DisposeBag()
self.rx.viewWillAppear
.map { _ in Void() }
.bind(to: viewModel.viewWillAppear)
.disposed(by: disposeBag)
viewModel.cellData
.drive(tableView.rx.items) { tv, row, data in
let index = IndexPath(row: row, section: 0)
let cell = tv.dequeueReusableCell(
withIdentifier: String(describing: BeerListCell.self),
for: index
) as! BeerListCell
cell.setData(data: data)
return cell
}
.disposed(by: disposeBag)
}
func attribute() { }
func layout() { }
71. class BeerListViewController: UIViewController {
let tableView = UITableView()
func bind(_ viewModel: BeerListViewBindable) {
self.disposeBag = DisposeBag()
self.rx.viewWillAppear
.map { _ in Void() }
.bind(to: viewModel.viewWillAppear)
.disposed(by: disposeBag)
viewModel.cellData
.drive(tableView.rx.items) { tv, row, data in
let index = IndexPath(row: row, section: 0)
let cell = tv.dequeueReusableCell(
withIdentifier: String(describing: BeerListCell.self),
for: index
) as! BeerListCell
cell.setData(data: data)
return cell
}
.disposed(by: disposeBag)
}
func attribute() { }
func layout() { }
let tableView = UITableView()
72. class BeerListViewController: UIViewController {
let tableView = UITableView()
func bind(_ viewModel: BeerListViewBindable) {
self.disposeBag = DisposeBag()
self.rx.viewWillAppear
.map { _ in Void() }
.bind(to: viewModel.viewWillAppear)
.disposed(by: disposeBag)
viewModel.cellData
.drive(tableView.rx.items) { tv, row, data in
let index = IndexPath(row: row, section: 0)
let cell = tv.dequeueReusableCell(
withIdentifier: String(describing: BeerListCell.self),
for: index
) as! BeerListCell
cell.setData(data: data)
return cell
}
.disposed(by: disposeBag)
}
func attribute() { }
func layout() { }
73. class BeerListViewController: UIViewController {
let tableView = UITableView()
func bind(_ viewModel: BeerListViewBindable) {
self.disposeBag = DisposeBag()
self.rx.viewWillAppear
.map { _ in Void() }
.bind(to: viewModel.viewWillAppear)
.disposed(by: disposeBag)
viewModel.cellData
.drive(tableView.rx.items) { tv, row, data in
let index = IndexPath(row: row, section: 0)
let cell = tv.dequeueReusableCell(
withIdentifier: String(describing: BeerListCell.self),
for: index
) as! BeerListCell
cell.setData(data: data)
return cell
}
.disposed(by: disposeBag)
}
func attribute() { }
func layout() { }
self.rx.viewWillAppear
.map { _ in Void() }
.bind(to: viewModel.viewWillAppear)
.disposed(by: disposeBag)
74. class BeerListViewController: UIViewController {
let tableView = UITableView()
func bind(_ viewModel: BeerListViewBindable) {
self.disposeBag = DisposeBag()
self.rx.viewWillAppear
.map { _ in Void() }
.bind(to: viewModel.viewWillAppear)
.disposed(by: disposeBag)
viewModel.cellData
.drive(tableView.rx.items) { tv, row, data in
let index = IndexPath(row: row, section: 0)
let cell = tv.dequeueReusableCell(
withIdentifier: String(describing: BeerListCell.self),
for: index
) as! BeerListCell
cell.setData(data: data)
return cell
}
.disposed(by: disposeBag)
}
func attribute() { }
func layout() { }
75. class BeerListViewController: UIViewController {
let tableView = UITableView()
func bind(_ viewModel: BeerListViewBindable) {
self.disposeBag = DisposeBag()
self.rx.viewWillAppear
.map { _ in Void() }
.bind(to: viewModel.viewWillAppear)
.disposed(by: disposeBag)
viewModel.cellData
.drive(tableView.rx.items) { tv, row, data in
let index = IndexPath(row: row, section: 0)
let cell = tv.dequeueReusableCell(
withIdentifier: String(describing: BeerListCell.self),
for: index
) as! BeerListCell
cell.setData(data: data)
return cell
}
.disposed(by: disposeBag)
}
func attribute() { }
func layout() { }
viewModel.cellData
.drive(tableView.rx.items) { }
.disposed(by: disposeBag)
76. protocol BeerListViewBindable {
//View -> ViewModel
var viewWillAppear: PublishSubject<Void> { get }
var willDisplayCell: PublishRelay<IndexPath> { get }
//ViewModel -> View
var cellData: Driver<[BeerListCell.Data]> { get }
var errorMessage: Signal<String> { get }
}
77. protocol BeerListViewBindable {
//View -> ViewModel
var viewWillAppear: PublishSubject<Void> { get }
var willDisplayCell: PublishRelay<IndexPath> { get }
//ViewModel -> View
var cellData: Driver<[BeerListCell.Data]> { get }
var errorMessage: Signal<String> { get }
}
78. protocol BeerListViewBindable {
//View -> ViewModel
var viewWillAppear: PublishSubject<Void> { get }
var willDisplayCell: PublishRelay<IndexPath> { get }
//ViewModel -> View
var cellData: Driver<[BeerListCell.Data]> { get }
var errorMessage: Signal<String> { get }
}
79. struct BeerListViewModel: BeerListViewBindable {
let disposeBag = DisposeBag()
let viewWillAppear = PublishSubject<Void>()
let cellData: Driver<[BeerListCell.Data]>
let willDisplayCell = PublishRelay<IndexPath>()
let errorMessage: Signal<String>
private var cells = BehaviorRelay<[Beer]>(value: [])
init(model: BeerListModel = BeerListModel()) {
80. struct BeerListViewModel: BeerListViewBindable {
init(model: BeerListModel = BeerListModel()) {
let beerListResult = viewWillAppear
.flatMapLatest(model.getBeerList)
.asObservable()
.share()
let beerListValue = beerListResult
.map { result -> [Beer]? in
guard case .success(let value) = result else {
return nil
}
return value
}
.filterNil()
let beerListError = beerListResult
.map { result -> String? in
guard case .failure(let error) = result else {
return nil
}
return error.message
}
.filterNil()
81. struct BeerListViewModel: BeerListViewBindable {
init(model: BeerListModel = BeerListModel()) {
let beerListResult = viewWillAppear
.flatMapLatest(model.getBeerList)
.asObservable()
.share()
let beerListValue = beerListResult
.map { result -> [Beer]? in
guard case .success(let value) = result else {
return nil
}
return value
}
.filterNil()
let beerListError = beerListResult
.map { result -> String? in
guard case .failure(let error) = result else {
return nil
}
return error.message
}
.filterNil()
82. struct BeerListViewModel: BeerListViewBindable {
init(model: BeerListModel = BeerListModel()) {
let beerListResult = viewWillAppear
.flatMapLatest(model.getBeerList)
.asObservable()
.share()
let beerListValue = beerListResult
.map { result -> [Beer]? in
guard case .success(let value) = result else {
return nil
}
return value
}
.filterNil()
let beerListError = beerListResult
.map { result -> String? in
guard case .failure(let error) = result else {
return nil
}
return error.message
}
.filterNil()
116. Summary
The memory models of RxSwift and Combine are very different.
Combine is really made for performance.
https://engineering.q42.nl/swift-combine-framework/