Slide 1

Slide 1 text

Reactive thinking in Swift 2016/12/01 CA.swift Ryo Aoyama / ra1028 #ca_swift

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

ແྉͰߴը࣭ͳੜ์ૹಈըΛݟ์୊ ੜ์ૹ഑৴ϓϥοτϑΥʔϜαʔϏε

Slide 4

Slide 4 text

FRP Functional Reactive Programming

Slide 5

Slide 5 text

RxSwift RxCocoa ReactiveSwift ReactiveCocoa

Slide 6

Slide 6 text

ɾඇಉظσʔλϑϩʔΛந৅Խ͠ɺ ౷ҰతͳΠϯλʔϑΣʔεͰ ѻ͏͜ͱ͕Ͱ͖Δ ɾঢ়ଶ؅ཧΛݮΒͤΔ ɾએݴతͳهड़͕Ͱ͖Δ Why use…?

Slide 7

Slide 7 text

ɾDelegate methods ɾCallback blocks ɾNotifications ɾControl actions ɾResponder chain events ɾKey-value observing (KVO) ɾFutures and promises

Slide 8

Slide 8 text

Event Stream Observable Signal SignalProducer

Slide 9

Slide 9 text

Event Stream?

Slide 10

Slide 10 text

Event Stream = Water pipe

Slide 11

Slide 11 text

Event Observable Signal SignalProducer Observe

Slide 12

Slide 12 text

Transforming

Slide 13

Slide 13 text

Map Next(x) Next(x * 10) Eventͷ࣋ͭ஋Λม׵͢Δ map

Slide 14

Slide 14 text

FlatMap Next(●) Next(■) 2ͭͷΠϕϯτετϦʔϜ Λ1ͭʹฏୱԽ͢Δ flatMap

Slide 15

Slide 15 text

Filtering

Slide 16

Slide 16 text

Filter ৚݅ʹ߹க͢Δ஋͚ͩΛ ड͚औΔ filter Next(5)

Slide 17

Slide 17 text

Combining

Slide 18

Slide 18 text

Merge ෳ਺ͷΠϕϯτετϦʔϜ ͷ஋ΛҰຊʹͯ͠ड͚औΔ merge

Slide 19

Slide 19 text

Sample with RxSwift UISegmentedControl, UITextField ͦΕͧΕͷΠϕϯτͰAPIϦΫΤετΛߦ͍ɺ ͦͷϨεϙϯεʹԠͯ͡UITableViewΛදࣔߋ৽͢Δɻ

Slide 20

Slide 20 text

without RxSwift

Slide 21

Slide 21 text

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 } }

Slide 22

Slide 22 text

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) } } } }

Slide 23

Slide 23 text

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 } }

Slide 24

Slide 24 text

ɾMutableͳม਺(ঢ়ଶ) ɾεϨου؅ཧ ɾॲཧͷϑϩʔ͕ෳࡶ ɾ֦ு͕೉͍͠ ɾControl Actions ɾCallback blocks ɾDelegate methods

Slide 25

Slide 25 text

with RxSwift

Slide 26

Slide 26 text

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) } }

Slide 27

Slide 27 text

ɾMutableͳม਺(ঢ়ଶ) ɾεϨου؅ཧ ɾॲཧͷϑϩʔ͕ෳࡶ ɾ֦ு͕೉͍͠ ɾControl Actions ɾCallback blocks ɾDelegate methods

Slide 28

Slide 28 text

Hot & Cold

Slide 29

Slide 29 text

Hot Observable Signal ɾߪಡऀͷ༗ແʹؔΘΒͣΠϕϯτ͕ྲྀΕΔ ɾෳ਺ߪಡ͞ΕΔͱετϦʔϜ͕෼ذ͢Δ

Slide 30

Slide 30 text

Subscribe Subscribe Subscribe Subscribe Hot

Slide 31

Slide 31 text

Hot let subject = PublishSubject() 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 ----- ग़ྗ ෼ذͤͨ̎ͭ͞ͷߪಡͰ ஋͕ڞ༗͞Ε͍ͯΔࣄ͕Θ͔Δ

Slide 32

Slide 32 text

Observable SignalProducer Cold ɾߪಡ͢Δ·Ͱಈ࡞͠ͳ͍ ɾߪಡ͞Εͨ਺͚ͩετϦʔϜ͕࡞ΒΕΔ

Slide 33

Slide 33 text

Subscribe Subscribe Subscribe Subscribe Cold

Slide 34

Slide 34 text

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 ------ ग़ྗ ̎ͭͷߪಡͰผʑͷ ετϦʔϜʹͳ͍ͬͯΔ

Slide 35

Slide 35 text

ColdͰॏ͍ॲཧ΍APIϦΫΤετΛ͍ͯ͠Δͱ…

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Cold -> Hot

Slide 38

Slide 38 text

Subscribe Subscribe Subscribe Subscribe Cold -> Hot

Slide 39

Slide 39 text

Subscribe Subscribe Subscribe Subscribe Cold -> Hot

Slide 40

Slide 40 text

// 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

Slide 41

Slide 41 text

Hotม׵͍ͯ͠Ε͹ɺColdͰॏ͍ॲཧ͕ߦΘΕͯ΋…

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

·ͱΊ

Slide 44

Slide 44 text

Event Stream ઃܭͷΠϯλʔϑΣʔε͕౷ҰԽɻ ࿈ଓతͳ஋ΛͲ͏ѻ͏͔͚ͩΛએݴతʹهड़͠ɺ ετϦʔϜಉ࢜Λ઀ଓ͢Δɻ = ඞવతʹMutableͳঢ়ଶ؅ཧ͸ݮΔɻ ίʔυྔͷ࡟ݮ͕࠷େͷར఺Ͱ͸ͳ͍ɻ

Slide 45

Slide 45 text

Hot & Cold Hot or ColdͰετϦʔϜ઀ଓͷ͞Εํ͕ҟͳΔɻ ҙࣝ͠ͳ͍ͱϦιʔεͷແବɾҙਤ͠ͳ͍ڍಈɾόά ͷՄೳੑ͕͋Δɻ RxSwiftͰ͸҉໧తɻ ReactiveSwiftͰ͸໌ࣔతɻ

Slide 46

Slide 46 text

͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠