Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Reactive thinking in Swift
Search
Ryo Aoyama
December 01, 2016
Programming
1
1.9k
Reactive thinking in Swift
presented at CA.swift #01
01 DEC. 2016
Ryo Aoyama
December 01, 2016
Tweet
Share
More Decks by Ryo Aoyama
See All by Ryo Aoyama
Micro Modular Architecture with Bazel
ra1028
9
1.7k
DifferenceKit in Action
ra1028
2
1.7k
Starting A11Y in iOS
ra1028
0
5k
Diffing inside SwiftUI List
ra1028
2
640
Integrate Combine into legacy frameworks
ra1028
2
620
Plasma - gRPC streamを利用したリアルタイムなユーザー体験
ra1028
3
1.5k
VueFlux - Flux inspired state managements
ra1028
3
1.5k
Optimizing Swift Collection
ra1028
2
3k
FRESH!配信アプリで採用した事・しなかった事
ra1028
8
4.9k
Other Decks in Programming
See All in Programming
エンジニア向け採用ピッチ資料
inusan
0
140
Blazing Fast UI Development with Compose Hot Reload (droidcon New York 2025)
zsmb
1
110
Enterprise Web App. Development (2): Version Control Tool Training Ver. 5.1
knakagawa
1
120
Benchmark
sysong
0
230
LINEヤフー データグループ紹介
lycorp_recruit_jp
0
760
データベースコネクションプール(DBCP)の変遷と理解
fujikawa8
1
270
Claude Codeの使い方
ttnyt8701
1
130
XP, Testing and ninja testing
m_seki
2
130
Haskell でアルゴリズムを抽象化する / 関数型言語で競技プログラミング
naoya
17
4.8k
PHP 8.4の新機能「プロパティフック」から学ぶオブジェクト指向設計とリスコフの置換原則
kentaroutakeda
1
310
F#で自在につくる静的ブログサイト - 関数型まつり2025
pizzacat83
0
310
GraphRAGの仕組みまるわかり
tosuri13
7
450
Featured
See All Featured
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
26k
Building Flexible Design Systems
yeseniaperezcruz
328
39k
Embracing the Ebb and Flow
colly
86
4.7k
How STYLIGHT went responsive
nonsquared
100
5.6k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
357
30k
Java REST API Framework Comparison - PWX 2021
mraible
31
8.6k
Rebuilding a faster, lazier Slack
samanthasiow
81
9k
Large-scale JavaScript Application Architecture
addyosmani
512
110k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
233
17k
A designer walks into a library…
pauljervisheath
206
24k
Speed Design
sergeychernyshev
31
1k
Into the Great Unknown - MozCon
thekraken
39
1.9k
Transcript
Reactive thinking in Swift 2016/12/01 CA.swift Ryo Aoyama / ra1028
#ca_swift
None
ແྉͰߴը࣭ͳੜ์ૹಈըΛݟ์ ੜ์ૹ৴ϓϥοτϑΥʔϜαʔϏε
FRP Functional Reactive Programming
RxSwift RxCocoa ReactiveSwift ReactiveCocoa
ɾඇಉظσʔλϑϩʔΛநԽ͠ɺ ౷ҰతͳΠϯλʔϑΣʔεͰ ѻ͏͜ͱ͕Ͱ͖Δ ɾঢ়ଶཧΛݮΒͤΔ ɾએݴతͳهड़͕Ͱ͖Δ Why use…?
ɾDelegate methods ɾCallback blocks ɾNotifications ɾControl actions ɾResponder chain events
ɾKey-value observing (KVO) ɾFutures and promises
Event Stream Observable<T> Signal<T, E> SignalProducer<T, E>
Event Stream?
Event Stream = Water pipe
Event Observable Signal SignalProducer Observe
Transforming
Map Next(x) Next(x * 10) Eventͷ࣋ͭΛม͢Δ map
FlatMap Next(•) Next(▪) 2ͭͷΠϕϯτετϦʔϜ Λ1ͭʹฏୱԽ͢Δ flatMap
Filtering
Filter ݅ʹ߹க͢Δ͚ͩΛ ड͚औΔ filter Next(5)
Combining
Merge ෳͷΠϕϯτετϦʔϜ ͷΛҰຊʹͯ͠ड͚औΔ merge
Sample with RxSwift UISegmentedControl, UITextField ͦΕͧΕͷΠϕϯτͰAPIϦΫΤετΛߦ͍ɺ ͦͷϨεϙϯεʹԠͯ͡UITableViewΛදࣔߋ৽͢Δɻ
without RxSwift
final class NonRxViewController: UIViewController { @IBOutlet fileprivate weak var segmentedControl:
UISegmentedControl! @IBOutlet fileprivate weak var textField: UITextField! @IBOutlet fileprivate weak var tableView: UITableView! fileprivate var items = [Item]() override func viewDidLoad() { super.viewDidLoad() tableView.register(SampleCell.self, forCellReuseIdentifier: "SampleCell") tableView.delegate = self tableView.dataSource = self } }
private extension NonRxViewController { @IBAction func segmentedControlChanged(_ sender: UISegmentedControl) {
getItemsAndReload() } @IBAction func textFieldChanged(_ sender: UITextField) { getItemsAndReload() } func getItemsAndReload() { let text = textField.text ?? "" let index = segmentedControl.selectedSegmentIndex Api.getItems(segmentIndex: index, text: text) { [weak self] items in let reload = { self?.items = items self?.tableView.reloadData() } if Thread.isMainThread { reload() } else { DispatchQueue.main.async(execute: reload) } } } }
extension NonRxViewController: UITableViewDelegate, UITableViewDataSource { func numberOfSections(in tableView: UITableView) ->
Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCell", for: indexPath) as! SampleCell let item = items[indexPath.item] cell.configure(with: item) return cell } }
ɾMutableͳม(ঢ়ଶ) ɾεϨουཧ ɾॲཧͷϑϩʔ͕ෳࡶ ɾ֦ு͕͍͠ ɾControl Actions ɾCallback blocks ɾDelegate methods
with RxSwift
final class RxViewController: UIViewController { @IBOutlet private weak var segmentedControl:
UISegmentedControl! @IBOutlet private weak var textField: UITextField! @IBOutlet private weak var tableView: UITableView! private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() tableView.register(SampleCell.self, forCellReuseIdentifier: "SampleCell") Observable.combineLatest( segmentedControl.rx.value, textField.rx.text.orEmpty.distinctUntilChanged()) { ($0, $1) } .skip(1) .flatMap(Api.getItems) .asDriver(onErrorJustReturn: []) .drive(tableView.rx.items(cellIdentifier: "SampleCell", cellType: SampleCell.self)) { _, item, cell in cell.configure(with: item) } .addDisposableTo(disposeBag) } }
ɾMutableͳม(ঢ়ଶ) ɾεϨουཧ ɾॲཧͷϑϩʔ͕ෳࡶ ɾ֦ு͕͍͠ ɾControl Actions ɾCallback blocks ɾDelegate methods
Hot & Cold
Hot Observable Signal ɾߪಡऀͷ༗ແʹؔΘΒͣΠϕϯτ͕ྲྀΕΔ ɾෳߪಡ͞ΕΔͱετϦʔϜ͕ذ͢Δ
Subscribe Subscribe Subscribe Subscribe Hot
Hot let subject = PublishSubject<Int>() let observer = subject.asObserver() let
randomInt = { Int(arc4random_uniform(100)) } // PublishSubjectΛasObservable()͢ΔͱHot ObservableʹͳΔ let hot = subject.asObservable() hot.subscribe(onNext: { print("Hot 1: \($0)") }) hot.subscribe(onNext: { print("Hot 2: \($0)") }) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") ----- 1: 20 2: 20 ----- 1: 30 2: 30 ----- 1: 93 2: 93 ----- ग़ྗ ذͤͨ̎ͭ͞ͷߪಡͰ ͕ڞ༗͞Ε͍ͯΔࣄ͕Θ͔Δ
Observable SignalProducer Cold ɾߪಡ͢Δ·Ͱಈ࡞͠ͳ͍ ɾߪಡ͞Ε͚ͨͩετϦʔϜ͕࡞ΒΕΔ
Subscribe Subscribe Subscribe Subscribe Cold
Cold // RxSwiftͰHot ObservableΛmap͢ΔͱCold ObservableʹͳΔ let cold = hot.map {
$0 + randomInt() } cold.subscribe(onNext: { print("Cold 1: \($0)") }) cold.subscribe(onNext: { print("Cold 2: \($0)") }) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") ------ 1: 182 2: 117 ------ 1: 84 2: 139 ------ 1: 106 2: 64 ------ ग़ྗ ̎ͭͷߪಡͰผʑͷ ετϦʔϜʹͳ͍ͬͯΔ
ColdͰॏ͍ॲཧAPIϦΫΤετΛ͍ͯ͠Δͱ…
let cold = hot.map { i -> Int in sleep(1)
// <- ॏ͍ॲཧ return i } cold.subscribe(onNext: { print("Cold 1: \($0), seconds: \(currentTimeSeconds())") }) cold.subscribe(onNext: { print("Cold 2: \($0), seconds: \(currentTimeSeconds())") }) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") ----------------------- Cold 1: 21, seconds: 49 Cold 2: 21, seconds: 50 ----------------------- Cold 1: 62, seconds: 51 Cold 2: 62, seconds: 52 ----------------------- Cold 1: 65, seconds: 53 Cold 2: 65, seconds: 54 ----------------------- ग़ྗ ੩తͳΛฦ͍ͯͯ͠ݟ্͔͚͕ڞ༗Ͱ͖͍ͯͯɺ ॲཧߪಡ͍ͯ͠Δ͚ͩߦΘΕ͍ͯΔ Cold
Cold -> Hot
Subscribe Subscribe Subscribe Subscribe Cold -> Hot
Subscribe Subscribe Subscribe Subscribe Cold -> Hot
// Cold ObervableΛshareReplay͢Δ͜ͱͰHot ObservableʹͳΔ let coldToHot = cold.shareReplay(1) coldToHot.subscribe(onNext: {
print("ColdToHot 1: \($0)") }) coldToHot.subscribe(onNext: { print("ColdToHot 2: \($0)") }) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") ----- 1: 32 2: 32 ----- 1: 54 2: 54 ----- 1: 144 2: 144 ----- ग़ྗ Λڞ༗͢ΔΑ͏ʹͳͬͨ Cold -> Hot
Hotม͍ͯ͠ΕɺColdͰॏ͍ॲཧ͕ߦΘΕͯ…
let cold = hot.map { i -> Int in sleep(1)
// <- ॏ͍ॲཧ return i } .shareReplay(1) cold.subscribe(onNext: { print("Cold 1: \($0), seconds: \(currentTimeSeconds())") }) cold.subscribe(onNext: { print("Cold 2: \($0), seconds: \(currentTimeSeconds())") }) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") observer.onNext(randomInt()) print("-----") ---------------------- Cold 1: 6, seconds: 7 Cold 2: 6, seconds: 7 ---------------------- Cold 1: 74, seconds: 8 Cold 2: 74, seconds: 8 ---------------------- Cold 1: 77, seconds: 9 Cold 2: 77, seconds: 9 ---------------------- ग़ྗ ෳߪಡ͍ͯͯ͠ ॲཧ͕ߦΘΕΔͷҰճ͚ͩ Cold -> Hot
·ͱΊ
Event Stream ઃܭͷΠϯλʔϑΣʔε͕౷ҰԽɻ ࿈ଓతͳΛͲ͏ѻ͏͔͚ͩΛએݴతʹهड़͠ɺ ετϦʔϜಉ࢜Λଓ͢Δɻ = ඞવతʹMutableͳঢ়ଶཧݮΔɻ ίʔυྔͷݮ͕࠷େͷརͰͳ͍ɻ
Hot & Cold Hot or ColdͰετϦʔϜଓͷ͞Εํ͕ҟͳΔɻ ҙࣝ͠ͳ͍ͱϦιʔεͷແବɾҙਤ͠ͳ͍ڍಈɾόά ͷՄೳੑ͕͋Δɻ RxSwiftͰ҉తɻ ReactiveSwiftͰ໌ࣔతɻ
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠