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
2.1k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Reactive thinking in Swift
presented at CA.swift #01
01 DEC. 2016
Ryo Aoyama
December 01, 2016
More Decks by Ryo Aoyama
See All by Ryo Aoyama
Micro Modular Architecture with Bazel
ra1028
9
1.9k
DifferenceKit in Action
ra1028
2
1.8k
Starting A11Y in iOS
ra1028
0
5.4k
Diffing inside SwiftUI List
ra1028
2
720
Integrate Combine into legacy frameworks
ra1028
2
670
Plasma - gRPC streamを利用したリアルタイムなユーザー体験
ra1028
3
1.6k
VueFlux - Flux inspired state managements
ra1028
3
1.6k
Optimizing Swift Collection
ra1028
2
3.2k
FRESH!配信アプリで採用した事・しなかった事
ra1028
8
5.1k
Other Decks in Programming
See All in Programming
初めての模倣学習とVLA
natsutan
0
330
AI駆動開発にグラフDBを重ねてみた
satoshi256kbyte
2
410
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
290
Webエンジニアなのにブラウザの仕組みがわからないので、Pythonで自作してみた
tatsuki12
4
1.2k
FastAPI の並行処理モデルを完全に理解する
hoto17296
9
3.3k
承認済みなのに差戻しできてしまうバグ、型で潰せます
shinchit
0
120
AWS Transform Customによる Spring Boot 2.xから4.xへのVerUp
satoshi256kbyte
2
110
GKE アップグレード前に知っておきたい Blue/Green と PDB の関係
stkk
0
110
「寝てても仕事が進む」Claude Codeで組む第二の脳
tomoyafujita2016
0
370
Jindong: Introducing Declarative Haptics in Compose Multiplatform
l2hyunwoo
0
130
Loosening the Reins: Go Generics Get More Flexible
kuro_kurorrr
0
350
AI Readyの正体はデータマネジメントだ メダリオン2.0の最前線
freee
PRO
0
450
Featured
See All Featured
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
310
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
550
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.3k
Balancing Empowerment & Direction
lara
6
1.3k
Become a Pro
speakerdeck
PRO
31
6.2k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
450
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
Leo the Paperboy
mayatellez
8
2.2k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
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Ͱ໌ࣔతɻ
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠