Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

RxSwift to Combine

87 views

Published on

Let'Swift 2019 컨퍼런스에서 RxSwift to Combine 이라고 발표한 자료입니다. 자료 내에 언급되는 코드 링크는 다음과 같습니다.
[BringMyOwnBeer]
• RxSwift/RxCocoa: https://github.com/fimuxd/BringMyOwnBeer-
• Combine/SwiftUI: https://github.com/fimuxd/BringMyOwnBeer-Combine

  • Be the first to comment

RxSwift to Combine

  1. 1. 뱅크샐러드박보영 RxSwift to Combine feat. SwiftUI
  2. 2. Introduce
  3. 3. Introduce
  4. 4. Introduce
  5. 5. Introduce
  6. 6. Introduce
  7. 7. Introduce Customize handling of asynchronous events by combining event-processing operators. https://developer.apple.com/documentation/combine
  8. 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. 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
  10. 10. Introduce
  11. 11. Index 개념 비교하기 예제로 확인하기 정리
  12. 12. 개념 비교하기
  13. 13. Asynchronous Interfaces •Target/Action •Notification Center •URLSession •KVO •Ad-hoc callbacks 왜? Combine https://developer.apple.com/videos/play/wwdc2019/722/
  14. 14. A unified declarative API for processing values over time Combine은, https://developer.apple.com/videos/play/wwdc2019/722/
  15. 15. Combine 핵심 요소 Publishers Subscribers Operators
  16. 16. Combine vs. RxSwift Publishers Subscribers Operators Observable Observer Operators
  17. 17. Publishers /Subscribers Observable /Observer Operator Operator Combine vs. RxSwift
  18. 18. Publishers /Subscribers Observable /Observer Subject Operator Operator Cancellable Subscribe(on:) Combine vs. RxSwift
  19. 19. Publishers /Subscribers Observable /Observer Subject Operator Subject Operator Cancellable Subscribe(on:) Combine vs. RxSwift
  20. 20. Publishers /Subscribers Observable /Observer Subject Operator Subject Operator Cancellable Disposable Subscribe(on:) Combine vs. RxSwift
  21. 21. Publishers /Subscribers Observable /Observer Subject Operator Subject Operator Cancellable Disposable subscribe(on:) subscribeOn(_:) Combine vs. RxSwift
  22. 22. Publisher vs. Observable • AnyPublisher •Observable
  23. 23. class Observable: ObservableType { } struct AnyPublisher: Publisher { } Publisher vs. Observable • AnyPublisher •Observable protocol Publisher { }⋯ ⋯ ⋯
  24. 24. Publisher vs. Observable • AnyPublisher •Observable protocol Publisher { } struct AnyPublisher: Publisher { } class Observable: ObservableType { } ⋯ ⋯ ⋯
  25. 25. Publisher vs. Observable • AnyPublisher • Value Type •Observable •Reference Type struct AnyPublisher: Publisher { } class Observable: ObservableType { } ⋯ ⋯
  26. 26. Publisher vs. Observable • AnyPublisher • Value Type • Output(Data type) • Failure(Error type) •Observable •Reference Type •Element(Data type) •❌
  27. 27. Publisher vs. Observable • AnyPublisher • Value Type • Output(Data type) • Failure(Error type) •Observable •Reference Type •Element(Data type) •❌ AnyPublisher<String, Error> Observable<Result<String, Error>>
  28. 28. Publisher vs. Observable • AnyPublisher • Value Type • Output(Data type) • Failure(Error type) •Observable •Reference Type •Element(Data type) •❌ AnyPublisher<String, Error> AnyPublisher<String, Never> Observable<Result<String, Error>> Observable<String>
  29. 29. Operators, RxSwift Only Combine에는 없는 Operators • amb() • asObserver() •concatMap • create • delaySubscription • dematerialize • enumerated •flatMapFirst • from • groupBy • ifEmpty(switchTo:) • interval • materialize • range • repeatElement • retryWhen • sample •withLatestFrom https://github.com/CombineCommunity/rxswift-to-combine-cheatsheet 👋
  30. 30. Operators, Combine Only Combine에만 있는 Operators • tryMap • tryScan • tryFilter • tryCompactMap • tryRemoveDuplicates(by:) • tryReduce • tryMax(by:) • tryMin(by:) • tryContains(where:) • tryAllSatisfy • tryDrop(while:) • tryPrefix(while:) • tryFirst(where:) • tryLast(where:) • tryCatch 👀
  31. 31. Map vs. tryMap func map<T>(_ transform: (Output) -> T) -> Just<T> func tryMap<T>(_ transform: (Output) throws -> T) -> Result<T, Error>.Publisher
  32. 32. Combine Operators • Merge, Merge3, Merge4, Merge5, Merge6, Merge7, Merge8, MergeMany • merge • combineLatest• CombineLatest, CombineLatest3, CombineLatest4 • zip• Zip, Zip3, Zip4
  33. 33. • ReplaySubject• ❌ • BehaviorSubject• CurrentValueSubject Subjects • PassthroughSubject • PublishSubject
  34. 34. • ReplaySubject• ❌ • BehaviorSubject• CurrentValueSubject Subjects • PassthroughSubject • PublishSubject class PassthroughSubject { public init() class PublishSubject { public override init() ⋯ ⋯
  35. 35. • ReplaySubject• ❌ • BehaviorSubject• CurrentValueSubject Subjects class CurrentValueSubject { public init(_ value: Output) class BehaviorSubject { public init(value: Element) ⋯ ⋯
  36. 36. 🧟 Cancellable vs. Disposable 🧟
  37. 37. 🧟 Cancellable vs. Disposable 🧟🗑 • Disposable • DisposeBag
  38. 38. 🧟 Cancellable vs. Disposable 🧟🗑 • Disposable • DisposeBag • Cancellable • AnyCancellable
  39. 39. Cancellable vs. Disposable NO DISPOSE BAG! 🗑 🙅
  40. 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. 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)
  42. 42. Thread Handling http://reactivex.io/documentation/operators/subscribeon.html
  43. 43. Thread Handling http://reactivex.io/documentation/operators/subscribeon.html Just(1) .subscribe(on: DispatchQueue.main) .map { _ in implements() } .sink { … }
  44. 44. Combine vs. RxSwift Publishers /Subscribers Observable /Observer Subject Operator Subject Operator Cancellable Disposable subscribe(on:) subscribeOn(_:)
  45. 45. WWDC 2019 •Introducing Combine https://developer.apple.com/videos/play/wwdc2019/722/ •Combine in Practice https://developer.apple.com/videos/play/wwdc2019/721/ •Modern Swift API Design https://developer.apple.com/videos/play/wwdc2019/415/ Reference
  46. 46. 예제로 확인하기
  47. 47. BringMyOwnBeer🍺
  48. 48. https://github.com/fimuxd/BringMyOwnBeer- PunkAPI by Brewdog List / Search / Random https://github.com/fimuxd/BringMyOwnBeer-Combine BringMyOwnBeer🍺
  49. 49. 📡
  50. 50. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() }
  51. 51. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() } import RxSwift import Combine
  52. 52. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() }
  53. 53. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() } Observable<Result<[Beer], PunkNetworkError>> AnyPublisher<[Beer], PunkNetworkError>
  54. 54. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() }
  55. 55. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() } session.rx.data session.dataTaskPublisher
  56. 56. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() }
  57. 57. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() } .decode(type:decoder:) do { try JSONDecoder().decode(_:from:) } catch { }
  58. 58. import RxSwift import Combine func getBeers(page: Int?) -> Observable<Result<[Beer], PunkNetworkError>> { return session.rx.data(request: URLRequest(url: url)) .map { data in do { let beers = try JSONDecoder().decode([Beer].self, from: data) return .success(beers) } catch { return .failure(.error("JSON parsing 에러”)) } } } func getBeers(page: Int?) -> AnyPublisher<[Beer], PunkNetworkError> { return session.dataTaskPublisher(for: URLRequest(url: url)) .flatMap { data in return Just(data.data) .decode(type: [Beer].self, decoder: JSONDecoder()) .mapError { _ in .error("JSON parsing 에러”) } } .eraseToAnyPublisher() }
  59. 59. 🍻
  60. 60. •리스트 형태 •GET beerList API 이용 •한 번에 25개의 맥주 정보 •최대 325개의 맥주 정보 Beer List
  61. 61. import RxSwift import RxCocoa Beer List with RxSwift
  62. 62. import RxSwift import RxCocoa Beer List with RxSwift ViewModel Bindable View
  63. 63. import RxSwift import RxCocoa Beer List with RxSwift View UIViewController
  64. 64. import RxSwift import RxCocoa Beer List with RxSwift View UIViewController UITableView
  65. 65. import RxSwift import RxCocoa Beer List with RxSwift View UIViewController UITableView UITableViewDelegate
  66. 66. import RxSwift import RxCocoa Beer List with RxSwift View func items<S, O>(_ source: O) -> (@escaping (UITableView, Int, S.Iterator.Element) -> UITableViewCell) -> Disposable where S : Sequence, S == O.E, O : ObservableType public var willDisplayCell: ControlEvent<WillDisplayCellEvent> { } UIViewController UITableView UITableViewDelegate
  67. 67. import RxSwift import RxCocoa Beer List with RxSwift View ViewModel Bindable UIViewController UITableView UITableViewDelegate
  68. 68. UIViewController UITableView UITableViewDelegate import RxSwift import RxCocoa Beer List with RxSwift View ViewModel Bindable var viewWillAppear: PublishSubject<Void> { get } var willDisplayCell: PublishRelay<IndexPath> { get }
  69. 69. UIViewController UITableView UITableViewDelegate import RxSwift import RxCocoa Beer List with RxSwift View ViewModel Bindable var cellData: Driver<[BeerListCell.Data]> { get } var errorMessage: Signal<String> { get }
  70. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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()
  83. 83. struct BeerListViewModel: BeerListViewBindable { Observable .merge( beerListValue, fetchedList ) .scan([]){ prev, newList in return newList.isEmpty ? [] : prev + newList } .bind(to: cells) .disposed(by: disposeBag) self.cellData = cells .map(model.parseData) .asDriver(onErrorDriveWith: .empty()) self.errorMessage = Observable .merge( beerListError, fetchedError ) .asSignal(onErrorJustReturn: .defaultError.message ?? “") }
  84. 84. import Combine import SwiftUI Beer List with Combine
  85. 85. UI Binding Combine SwiftUI RxSwift RxCocoa
  86. 86. View 는, https://developer.apple.com/videos/play/wwdc2019/226 Views are a function of state, not of a sequence of events.
  87. 87. Property Wrapper @State @ObservableObject https://developer.apple.com/videos/play/wwdc2019/226
  88. 88. Property Wrapper @State @ObservableObject https://developer.apple.com/videos/play/wwdc2019/226 View-local External
  89. 89. Property Wrapper @State @ObservableObject https://developer.apple.com/videos/play/wwdc2019/226 View-local External prev) BindableObject
  90. 90. Property Wrapper @State @ObservableObject https://developer.apple.com/videos/play/wwdc2019/226 View-local Value type External Reference type prev) BindableObject
  91. 91. Property Wrapper @State @ObservableObject https://developer.apple.com/videos/play/wwdc2019/226 View-local Value type Framework Managed External Reference type Developer Managed prev) BindableObject
  92. 92. 🧑💻 SwiftUI Action User Interaction https://developer.apple.com/videos/play/wwdc2019/226 Data Flow through SwiftUI
  93. 93. 🧑💻 SwiftUI Action @StateMutation User Interaction https://developer.apple.com/videos/play/wwdc2019/226 Data Flow through SwiftUI
  94. 94. 🧑💻 SwiftUI Action View @State UpdatesMutation User Interaction https://developer.apple.com/videos/play/wwdc2019/226 Data Flow through SwiftUI
  95. 95. 🧑💻 SwiftUI Action View @State Render UpdatesMutation User Interaction https://developer.apple.com/videos/play/wwdc2019/226 Data Flow through SwiftUI
  96. 96. 🧑💻 SwiftUI Action @State Render UpdatesMutation User Interaction https://developer.apple.com/videos/play/wwdc2019/226 🧨 🍺 @Publisher View Data Flow through SwiftUI
  97. 97. import SwiftUI struct BeerList: View { @ObservedObject var viewModel: BeerListViewModel init(viewModel: BeerListViewModel) { self.viewModel = viewModel } var body: some View { NavigationView { List(viewModel.beers, id: .id) { beer in BeerRow(beer: beer).onAppear { self.viewModel.appearedID.send(beer.id) } } .navigationBarTitle(Text("맥주리스트")) } .alert(isPresented: $viewModel.showingAlert) { Alert(title: Text(viewModel.errorMessage)) } } }
  98. 98. struct BeerList: View { @ObservedObject var viewModel: BeerListViewModel init(viewModel: BeerListViewModel) { self.viewModel = viewModel } var body: some View { NavigationView { List(viewModel.beers, id: .id) { beer in BeerRow(beer: beer).onAppear { self.viewModel.appearedID.send(beer.id) } } .navigationBarTitle(Text("맥주리스트")) } .alert(isPresented: $viewModel.showingAlert) { Alert(title: Text(viewModel.errorMessage)) } } }
  99. 99. class BeerListViewModel: ObservableObject { @Published var beers: [Beer] = [] @Published var showingAlert: Bool = false @Published var errorMessage: String = "" let appearedID = PassthroughSubject<Int?, PunkNetworkError>() private var cancellables = Set<AnyCancellable>() init(model: BeerListModel = BeerListModel()) {
  100. 100. class BeerListViewModel: ObservableObject { //ViewModel -> View @Published var beers: [Beer] = [] @Published var showingAlert: Bool = false @Published var errorMessage: String = "" //View -> ViewModel let appearedID = PassthroughSubject<Int?, PunkNetworkError>() private var cancellables = Set<AnyCancellable>() init(model: BeerListModel = BeerListModel()) {
  101. 101. class BeerListViewModel: ObservableObject { //ViewModel -> View @Published var beers: [Beer] = [] @Published var showingAlert: Bool = false @Published var errorMessage: String = "" //View -> ViewModel let appearedID = PassthroughSubject<Int?, PunkNetworkError>() private var cancellables = Set<AnyCancellable>() init(model: BeerListModel = BeerListModel()) {
  102. 102. class BeerListViewModel: ObservableObject { init(model: BeerListModel = BeerListModel()) { let loadBeerList = appearedID .map { model.getPageToPatch(beers: self.beers, id: $0) } .filter { $0 != nil } .eraseToAnyPublisher()
  103. 103. class BeerListViewModel: ObservableObject { init(model: BeerListModel = BeerListModel()) { let loadBeerList = appearedID .map { model.getPageToPatch(beers: self.beers, id: $0) } .filter { $0 != nil } .eraseToAnyPublisher() .eraseToAnyPublisher() .asObservable()
  104. 104. class BeerListViewModel: ObservableObject { .eraseToAnyPublisher() loadBeerList .prepend(nil) .flatMap(model.getBeerList) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { guard case .failure(let error) = $0 else { return } self.beers = [] self.showingAlert = true self.errorMessage = error.message ?? "에러 발생🚨” }, receiveValue: { beers in self.beers += beers }) .store(in: &cancellables) } }
  105. 105. class BeerListViewModel: ObservableObject { .eraseToAnyPublisher() loadBeerList .prepend(nil) .flatMap(model.getBeerList) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { guard case .failure(let error) = $0 else { return } self.beers = [] self.showingAlert = true self.errorMessage = error.message ?? "에러 발생🚨” }, receiveValue: { beers in self.beers += beers }) .store(in: &cancellables) } }
  106. 106. class BeerListViewModel: ObservableObject { .eraseToAnyPublisher() loadBeerList .prepend(nil) .flatMap(model.getBeerList) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { guard case .failure(let error) = $0 else { return } self.beers = [] self.showingAlert = true self.errorMessage = error.message ?? "에러 발생🚨” }, receiveValue: { beers in self.beers += beers }) .store(in: &cancellables) } }
  107. 107. class BeerListViewModel: ObservableObject { .eraseToAnyPublisher() loadBeerList .prepend(nil) .flatMap(model.getBeerList) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { guard case .failure(let error) = $0 else { return } self.beers = [] self.showingAlert = true self.errorMessage = error.message ?? "에러 발생🚨” }, receiveValue: { beers in self.beers += beers }) .store(in: &cancellables) } } .sink( receiveCompletion: receiveValue: ) .subscribe( onNext: onError: onCompleted: onDisposed: )
  108. 108. class BeerListViewModel: ObservableObject { .eraseToAnyPublisher() loadBeerList .prepend(nil) .flatMap(model.getBeerList) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { guard case .failure(let error) = $0 else { return } self.beers = [] self.showingAlert = true self.errorMessage = error.message ?? "에러 발생🚨” }, receiveValue: { beers in self.beers += beers }) .store(in: &cancellables) } } @Published var beers: [Beer] = []
  109. 109. import SwiftUI struct BeerList: View { @ObservedObject var viewModel: BeerListViewModel init(viewModel: BeerListViewModel) { self.viewModel = viewModel } var body: some View { NavigationView { List(viewModel.beers, id: .id) { beer in BeerRow(beer: beer).onAppear { self.viewModel.appearedID.send(beer.id) } } .navigationBarTitle(Text("맥주리스트")) } .alert(isPresented: $viewModel.showingAlert) { Alert(title: Text(viewModel.errorMessage)) } } }
  110. 110. import SwiftUI struct BeerList: View { @ObservedObject var viewModel: BeerListViewModel init(viewModel: BeerListViewModel) { self.viewModel = viewModel } var body: some View { NavigationView { List(viewModel.beers, id: .id) { beer in BeerRow(beer: beer).onAppear { self.viewModel.appearedID.send(beer.id) } } .navigationBarTitle(Text("맥주리스트")) } .alert(isPresented: $viewModel.showingAlert) { Alert(title: Text(viewModel.errorMessage)) } } } struct List: View { public init( _ data: Data, id: KeyPath<Data.Element, ID>, ⋯
  111. 111. BringMyOwnBeer🍺
  112. 112. 정리
  113. 113. Combine/SwiftUI RxSwift/RxCocoa Summary
  114. 114. App Size 0MB 1MB 2MB 3MB 4MB Combine RxSwift Summary
  115. 115. Summary https://medium.com/flawless-app-stories/will-combine-kill-rxswift-64780a150d89
  116. 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/
  117. 117. Summary 👨💻 ❤ RxSwift
  118. 118. Summary 👨💻 ❤ Combine
  119. 119. Summary 👨💻 ❤ Combine 👩💻 ❓ RxSwift
  120. 120. Summary 👨💻 ❤ Combine 👩💻 ❤ Combine
  121. 121. Summary 👨💻 ❤ Combine 👩💻 ❤ Combine 🎁 SwiftUI ✨ ✨ ✨ ✨ ✨
  122. 122. Summary 👨💻 ❤ Combine👩💻 ❤ Combine 🎁 SwiftUI
  123. 123. 🎁 SwiftUI 👨💻 ❤ Combine👩💻 ❤ Combine Summary iOS 13.0+ ⚡⚡⚡ ⚡⚡⚡ ⚡⚡⚡
  124. 124. 완벽함이란 더 이상 더할 것이 없는 상태가 아니라, 더 이상 뺄 것이 없는 상태를 말한다. Antoine de Saint-Exupéry

×