SlideShare a Scribd company logo
뱅크샐러드박보영
RxSwift to Combine
feat. SwiftUI
Introduce
Introduce
Introduce
Introduce
Introduce
Introduce
Customize handling
of asynchronous events
by combining event-processing operators.
https://developer.apple.com/documentation/combine
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
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
Introduce
Index
개념
비교하기
예제로
확인하기
정리
개념 비교하기
Asynchronous Interfaces
•Target/Action
•Notification Center
•URLSession
•KVO
•Ad-hoc callbacks
왜? Combine
https://developer.apple.com/videos/play/wwdc2019/722/
A unified declarative API for
processing values over time
Combine은,
https://developer.apple.com/videos/play/wwdc2019/722/
Combine 핵심 요소
Publishers Subscribers Operators
Combine vs. RxSwift
Publishers Subscribers Operators
Observable Observer Operators
Publishers
/Subscribers
Observable
/Observer
Operator Operator
Combine vs. RxSwift
Publishers
/Subscribers
Observable
/Observer
Subject
Operator Operator
Cancellable
Subscribe(on:)
Combine vs. RxSwift
Publishers
/Subscribers
Observable
/Observer
Subject
Operator
Subject
Operator
Cancellable
Subscribe(on:)
Combine vs. RxSwift
Publishers
/Subscribers
Observable
/Observer
Subject
Operator
Subject
Operator
Cancellable Disposable
Subscribe(on:)
Combine vs. RxSwift
Publishers
/Subscribers
Observable
/Observer
Subject
Operator
Subject
Operator
Cancellable Disposable
subscribe(on:) subscribeOn(_:)
Combine vs. RxSwift
Publisher vs. Observable
• AnyPublisher • Observable
class Observable: ObservableType { }
struct AnyPublisher: Publisher { }
Publisher vs. Observable
• AnyPublisher • Observable
protocol Publisher { }⋯
⋯
⋯
Publisher vs. Observable
• AnyPublisher • Observable
protocol Publisher { }
struct AnyPublisher: Publisher { }
class Observable: ObservableType { }
⋯
⋯
⋯
Publisher vs. Observable
• AnyPublisher
• Value Type
• Observable
• Reference Type
struct AnyPublisher: Publisher { }
class Observable: ObservableType { }
⋯
⋯
Publisher vs. Observable
• AnyPublisher
• Value Type
• Output(Data type)
• Failure(Error type)
• Observable
• Reference Type
• Element(Data type)
• ❌
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>>
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>
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
👋
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
👀
Map vs. tryMap
func map<T>(_ transform: (Output) -> T)
-> Just<T>
func tryMap<T>(_ transform: (Output) throws -> T)
-> Result<T, Error>.Publisher
Combine Operators
• Merge, Merge3,
Merge4, Merge5,
Merge6, Merge7,
Merge8, MergeMany
• merge
• combineLatest• CombineLatest,
CombineLatest3,
CombineLatest4
• zip• Zip, Zip3, Zip4
• ReplaySubject• ❌
• BehaviorSubject• CurrentValueSubject
Subjects
• PassthroughSubject • PublishSubject
• ReplaySubject• ❌
• BehaviorSubject• CurrentValueSubject
Subjects
• PassthroughSubject • PublishSubject
class PassthroughSubject {
public init()
class PublishSubject {
public override init()
⋯
⋯
• ReplaySubject• ❌
• BehaviorSubject• CurrentValueSubject
Subjects
class CurrentValueSubject {
public init(_ value: Output)
class BehaviorSubject {
public init(value: Element)
⋯
⋯
🧟
Cancellable vs. Disposable
🧟
🧟
Cancellable vs. Disposable
🧟🗑 • Disposable
• DisposeBag
🧟
Cancellable vs. Disposable
🧟🗑 • Disposable
• DisposeBag
• Cancellable
• AnyCancellable
Cancellable vs. Disposable
NO DISPOSE BAG!
🗑 🙅
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)
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)
Thread Handling
http://reactivex.io/documentation/operators/subscribeon.html
Thread Handling
http://reactivex.io/documentation/operators/subscribeon.html
Just(1)
.subscribe(on: DispatchQueue.main)
.map { _ in
implements()
}
.sink { … }
Combine vs. RxSwift
Publishers
/Subscribers
Observable
/Observer
Subject
Operator
Subject
Operator
Cancellable Disposable
subscribe(on:) subscribeOn(_:)
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
예제로 확인하기
BringMyOwnBeer🍺
https://github.com/fimuxd/BringMyOwnBeer-
PunkAPI by Brewdog
List / Search / Random
https://github.com/fimuxd/BringMyOwnBeer-Combine
BringMyOwnBeer🍺
📡
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
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
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
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>
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
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
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
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 { }
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()
}
🍻
•리스트 형태
•GET beerList API 이용
•한 번에 25개의 맥주 정보
•최대 325개의 맥주 정보
Beer List
import RxSwift
import RxCocoa
Beer List with RxSwift
import RxSwift
import RxCocoa
Beer List with RxSwift
ViewModel
Bindable
View
import RxSwift
import RxCocoa
Beer List with RxSwift
View
UIViewController
import RxSwift
import RxCocoa
Beer List with RxSwift
View
UIViewController
UITableView
import RxSwift
import RxCocoa
Beer List with RxSwift
View
UIViewController
UITableView
UITableViewDelegate
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
import RxSwift
import RxCocoa
Beer List with RxSwift
View
ViewModel
Bindable
UIViewController
UITableView
UITableViewDelegate
UIViewController
UITableView
UITableViewDelegate
import RxSwift
import RxCocoa
Beer List with RxSwift
View
ViewModel
Bindable
var viewWillAppear: PublishSubject<Void> { get }
var willDisplayCell: PublishRelay<IndexPath> { get }
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 }
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() { }
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()
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() { }
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)
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() { }
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)
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 }
}
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 }
}
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 }
}
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()) {
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()
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()
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()
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 ?? “")
}
import Combine
import SwiftUI
Beer List with Combine
UI Binding
Combine
SwiftUI
RxSwift
RxCocoa
View 는,
https://developer.apple.com/videos/play/wwdc2019/226
Views are a function of state,
not of a sequence of events.
Property Wrapper
@State @ObservableObject
https://developer.apple.com/videos/play/wwdc2019/226
Property Wrapper
@State @ObservableObject
https://developer.apple.com/videos/play/wwdc2019/226
View-local External
Property Wrapper
@State @ObservableObject
https://developer.apple.com/videos/play/wwdc2019/226
View-local External
prev) BindableObject
Property Wrapper
@State @ObservableObject
https://developer.apple.com/videos/play/wwdc2019/226
View-local
Value type
External
Reference type
prev) BindableObject
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
🧑💻
SwiftUI
Action
User Interaction
https://developer.apple.com/videos/play/wwdc2019/226
Data Flow through SwiftUI
🧑💻
SwiftUI
Action
@StateMutation
User Interaction
https://developer.apple.com/videos/play/wwdc2019/226
Data Flow through SwiftUI
🧑💻
SwiftUI
Action View
@State UpdatesMutation
User Interaction
https://developer.apple.com/videos/play/wwdc2019/226
Data Flow through SwiftUI
🧑💻
SwiftUI
Action View
@State
Render
UpdatesMutation
User Interaction
https://developer.apple.com/videos/play/wwdc2019/226
Data Flow through SwiftUI
🧑💻
SwiftUI
Action
@State
Render
UpdatesMutation
User Interaction
https://developer.apple.com/videos/play/wwdc2019/226
🧨 🍺
@Publisher
View
Data Flow through SwiftUI
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 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))
}
}
}
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()) {
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()) {
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()) {
class BeerListViewModel: ObservableObject {
init(model: BeerListModel = BeerListModel()) {
let loadBeerList = appearedID
.map { model.getPageToPatch(beers: self.beers, id: $0) }
.filter { $0 != nil }
.eraseToAnyPublisher()
class BeerListViewModel: ObservableObject {
init(model: BeerListModel = BeerListModel()) {
let loadBeerList = appearedID
.map { model.getPageToPatch(beers: self.beers, id: $0) }
.filter { $0 != nil }
.eraseToAnyPublisher()
.eraseToAnyPublisher() .asObservable()
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)
}
}
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)
}
}
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)
}
}
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:
)
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] = []
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))
}
}
}
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>,
⋯
BringMyOwnBeer🍺
정리
Combine/SwiftUI RxSwift/RxCocoa
Summary
App Size
0MB
1MB
2MB
3MB
4MB
Combine RxSwift
Summary
Summary
https://medium.com/flawless-app-stories/will-combine-kill-rxswift-64780a150d89
Summary
The memory models of RxSwift and Combine are very different.
Combine is really made for performance.
https://engineering.q42.nl/swift-combine-framework/
Summary
👨💻 ❤ RxSwift
Summary
👨💻 ❤ Combine
Summary
👨💻 ❤ Combine
👩💻 ❓ RxSwift
Summary
👨💻 ❤ Combine
👩💻 ❤ Combine
Summary
👨💻 ❤ Combine
👩💻 ❤ Combine
🎁 SwiftUI
✨
✨ ✨
✨ ✨
Summary
👨💻
❤
Combine👩💻
❤
Combine
🎁 SwiftUI
🎁 SwiftUI
👨💻
❤
Combine👩💻
❤
Combine
Summary
iOS 13.0+
⚡⚡⚡
⚡⚡⚡
⚡⚡⚡
RxSwift to Combine
완벽함이란 더 이상 더할 것이 없는 상태가 아니라,
더 이상 뺄 것이 없는 상태를 말한다.
Antoine de Saint-Exupéry
RxSwift to Combine

More Related Content

PPTX
Installing and Running Postfix within a Docker Container
ODP
An Introduction to Vuejs
PDF
Dockerfile
PDF
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
PDF
Vue.js for beginners
PPTX
DevOps Tutorial For Beginners | DevOps Tutorial | DevOps Tools | DevOps Train...
PPTX
PDF
[TECHCON 2019: MOBILE - Android]3.안드로이드 개발자 로드맵
Installing and Running Postfix within a Docker Container
An Introduction to Vuejs
Dockerfile
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Vue.js for beginners
DevOps Tutorial For Beginners | DevOps Tutorial | DevOps Tools | DevOps Train...
[TECHCON 2019: MOBILE - Android]3.안드로이드 개발자 로드맵

What's hot (20)

PDF
Vue, vue router, vuex
PPTX
Component lifecycle hooks in Angular 2.0
PDF
[AIS 2018] [Team Tools_Basic] Confluence는 어떻게 쓰나요 - 모우소프트
PDF
Jenkins Pipeline Tutorial | Continuous Delivery Pipeline Using Jenkins | DevO...
PPTX
Getting started with Docker
PDF
Gitlab ci-cd
PDF
Introduction to docker
PPTX
Jenkins CI presentation
PDF
Selenium Automation Testing Interview Questions And Answers
PPTX
PDF
Introduction to docker
PDF
How to write a Dockerfile
PDF
Jenkins
PDF
Introduction to Docker storage, volume and image
PPTX
Leveraging Azure DevOps across the Enterprise
PDF
Terraform introduction
PDF
Py.test
PPTX
Vue js for beginner
PDF
Crossplane @ Mastering GitOps.pdf
PPT
Git installation and configuration
Vue, vue router, vuex
Component lifecycle hooks in Angular 2.0
[AIS 2018] [Team Tools_Basic] Confluence는 어떻게 쓰나요 - 모우소프트
Jenkins Pipeline Tutorial | Continuous Delivery Pipeline Using Jenkins | DevO...
Getting started with Docker
Gitlab ci-cd
Introduction to docker
Jenkins CI presentation
Selenium Automation Testing Interview Questions And Answers
Introduction to docker
How to write a Dockerfile
Jenkins
Introduction to Docker storage, volume and image
Leveraging Azure DevOps across the Enterprise
Terraform introduction
Py.test
Vue js for beginner
Crossplane @ Mastering GitOps.pdf
Git installation and configuration
Ad

Similar to RxSwift to Combine (12)

PDF
RxSwift to Combine
PDF
LetSwift RxSwift 시작하기
PPTX
Apple.combine
PDF
Compose Async with RxJS
PDF
Combine Framework
PDF
Reactive Programming with RxSwift
PDF
Intro to Reactive Programming with Swift
PDF
Combine vs RxSwift
PDF
Reactive programming with RxSwift
PDF
Reactive Thinking in iOS Development - Pedro Piñera Buendía - Codemotion Amst...
PPTX
Rx for Android & iOS by Harin Trivedi
PDF
Cascadia.js: Don't Cross the Streams
RxSwift to Combine
LetSwift RxSwift 시작하기
Apple.combine
Compose Async with RxJS
Combine Framework
Reactive Programming with RxSwift
Intro to Reactive Programming with Swift
Combine vs RxSwift
Reactive programming with RxSwift
Reactive Thinking in iOS Development - Pedro Piñera Buendía - Codemotion Amst...
Rx for Android & iOS by Harin Trivedi
Cascadia.js: Don't Cross the Streams
Ad

Recently uploaded (20)

PPTX
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PPTX
history of c programming in notes for students .pptx
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
System and Network Administration Chapter 2
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
Materi_Pemrograman_Komputer-Looping.pptx
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PPTX
Essential Infomation Tech presentation.pptx
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
AI in Product Development-omnex systems
PPTX
Odoo POS Development Services by CandidRoot Solutions
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PPTX
ai tools demonstartion for schools and inter college
PPTX
Operating system designcfffgfgggggggvggggggggg
PDF
Digital Strategies for Manufacturing Companies
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PPT
Introduction Database Management System for Course Database
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
history of c programming in notes for students .pptx
Design an Analysis of Algorithms II-SECS-1021-03
System and Network Administration Chapter 2
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Materi_Pemrograman_Komputer-Looping.pptx
PTS Company Brochure 2025 (1).pdf.......
Which alternative to Crystal Reports is best for small or large businesses.pdf
Essential Infomation Tech presentation.pptx
Design an Analysis of Algorithms I-SECS-1021-03
AI in Product Development-omnex systems
Odoo POS Development Services by CandidRoot Solutions
2025 Textile ERP Trends: SAP, Odoo & Oracle
ai tools demonstartion for schools and inter college
Operating system designcfffgfgggggggvggggggggg
Digital Strategies for Manufacturing Companies
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Introduction Database Management System for Course Database
How to Migrate SBCGlobal Email to Yahoo Easily

RxSwift to Combine