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
5.1k
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
新メンバーも今日から大活躍!SREが支えるスケールし続ける組織のオンボーディング
honmarkhunt
5
9.2k
商品比較サービス「マイベスト」における パーソナライズレコメンドの第一歩
ucchiii43
0
190
おやつのお供はお決まりですか?@WWDC25 Recap -Japan-\(region).swift
shingangan
0
150
The Modern View Layer Rails Deserves: A Vision For 2025 And Beyond @ RailsConf 2025, Philadelphia, PA
marcoroth
2
780
20250708_JAWS_opscdk
takuyay0ne
2
140
型で語るカタ
irof
0
770
「テストは愚直&&網羅的に書くほどよい」という誤解 / Test Smarter, Not Harder
munetoshi
1
220
リバースエンジニアリング新時代へ! GhidraとClaude DesktopをMCPで繋ぐ/findy202507
tkmru
4
1.2k
状態遷移図を書こう / Sequence Chart vs State Diagram
orgachem
PRO
2
240
“いい感じ“な定量評価を求めて - Four Keysとアウトカムの間の探求 -
nealle
2
12k
生成AI時代のコンポーネントライブラリの作り方
touyou
1
300
チームで開発し事業を加速するための"良い"設計の考え方 @ サポーターズCoLab 2025-07-08
agatan
1
480
Featured
See All Featured
Documentation Writing (for coders)
carmenintech
72
4.9k
Building Applications with DynamoDB
mza
95
6.5k
Build The Right Thing And Hit Your Dates
maggiecrowley
37
2.8k
Designing for Performance
lara
610
69k
Building an army of robots
kneath
306
45k
VelocityConf: Rendering Performance Case Studies
addyosmani
332
24k
Java REST API Framework Comparison - PWX 2021
mraible
31
8.7k
Faster Mobile Websites
deanohume
308
31k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
Become a Pro
speakerdeck
PRO
29
5.4k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
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Ͱ໌ࣔతɻ
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠