Slide 1

Slide 1 text

Swiftͱ Functional Reactive Programming 2018.10.02 iOSLT / Shota Nakagami (@shtnkgm)

Slide 2

Slide 2 text

ຊ೔ͷΰʔϧ • ؔ਺ܕϦΞΫςΟϒϓϩάϥϛϯάΛ;Θͬͱ஌Δ • RxSwiftͰͷ࣮૷Πϝʔδ͕༙͘

Slide 3

Slide 3 text

ؔ਺ܕϦΞΫςΟϒϓϩάϥϛϯά • Functional Reactive ProgrammingʢFRPʣ • FRP͸ϓϩάϥϛϯάύϥμΠϜͷҰͭ • ؔ਺ܕϓϩάϥϛϯά + ϦΞΫςΟϒϓϩάϥϛϯά

Slide 4

Slide 4 text

ϓϩάϥϛϯάύϥμΠϜ!

Slide 5

Slide 5 text

SwiftΫΠζ

Slide 6

Slide 6 text

1͔Β10·Ͱͷ੔਺Λ3ഒͯ͠ɺ 6ͷഒ਺ͷΈ഑ྻΛੜ੒

Slide 7

Slide 7 text

ίʔυ͕ࢥ͍ු͔ͼ·͔ͨ͠?

Slide 8

Slide 8 text

ղ౴ྫ1 1͔Β10·Ͱͷ੔਺Λ3ഒͯ͠ɺ6ͷഒ਺ͷΈ഑ྻΛੜ੒ var result = [Int]() for number in 1...10 { let tripleNumber = number * 3 if tripleNumber % 6 == 0 { result += [tripleNumber] } }

Slide 9

Slide 9 text

ղ౴ྫ2 1͔Β10·Ͱͷ੔਺Λ3ഒͯ͠ɺ6ͷഒ਺ͷΈ഑ྻΛੜ੒ let result = (1...10).map { $0 * 3 }.filter { $0 % 6 == 0 }

Slide 10

Slide 10 text

Ξϓϩʔνͷ࢓ํͷҧ͍ || ϓϩάϥϛϯάύϥμΠϜ

Slide 11

Slide 11 text

ղ౴ྫ1ʣ໋ྩܕϓϩάϥϛϯά var result = [Int]() for number in 1...10 { let tripleNumber = number * 3 if tripleNumber % 6 == 0 { result += [tripleNumber] } } ղ౴ྫ2ʣએݴܕϓϩάϥϛϯάʢதͰ΋ؔ਺ܕʣ let result = (1...10).map { $0 * 3 }.filter { $0 % 6 == 0 }

Slide 12

Slide 12 text

໋ྩܕ ! એݴܕ " ண໨ How ʢखଓ͖ʣ What ʢग़ྗͷੑ࣭ʣ ߏ଄ forϧʔϓ/৚݅෼ذ Λଟ༻ ؔ਺Λܨ͛Δ ʢؔ਺ܕʣ ঢ়ଶͷมߋ varʢεςʔτϑϧʣ letʢεςʔτϨεʣ

Slide 13

Slide 13 text

ؔ਺ܕͰॻ͘ͱ ಡΈ΍͍͢ ঢ়ଶ΋ݮΒͤΔ͠ɺແବͷม਺ͷ໋໊΋ෆཁ

Slide 14

Slide 14 text

ϦΞΫςΟϒϓϩάϥϛϯά!

Slide 15

Slide 15 text

ϦΞΫςΟϒϓϩάϥϛϯά • ඇಉظʹมԽ͢Δ஋ͷؔ܎ੑΛએݴతʹهड़ • Ϣʔβʔૢ࡞΍APIͷ௨৴݁ՌͳͲඇಉظͳ΋ͷ ྫʣߋ৽Ϙλϯλοϓ -> APIϦΫΤετ -> ը໘ߋ৽

Slide 16

Slide 16 text

RxSwift

Slide 17

Slide 17 text

RxSwift • FRPΛ࣮ݱ͢ΔͨΊͷϑϨʔϜϫʔΫ • iOSΞϓϦ։ൃͰ͸RxCocoa΋ʢUIKitΛ֦ுʣ • ඇಉظʹมԽ͢Δ஋ΛObservableͱͯ͠ந৅Խ

Slide 18

Slide 18 text

Observable !

Slide 19

Slide 19 text

Observable • ΠϕϯτͷྲྀΕʢετϦʔϜʣ • ઒ʹྫ͑ΒΕΔ • ྲྀΕͯ͘ΔΠϕϯτ͸3छྨ͚ͩ • nextʢͳʹ͔ͷ஋ʣ • errorʢΤϥʔऴྃʣ • completedʢ׬ྃʣ

Slide 20

Slide 20 text

͔͜͜Β͸RxSwiftͷίʔυΛަ͑ͨྫ

Slide 21

Slide 21 text

੔਺ετϦʔϜ

Slide 22

Slide 22 text

// Πϝʔδ ---1---2---3---4---|(completed)---> // ίʔυ Observable .of(1, 2, 3, 4) // ετϦʔϜΛੜ੒ .subscribe { print($0) } // ετϦʔϜΛ؂ࢹ .dispose() // ετϦʔϜΛഁغ

Slide 23

Slide 23 text

ϘλϯλοϓετϦʔϜ λοϓͨ͠Βίϯιʔϧग़ྗ

Slide 24

Slide 24 text

// Πϝʔδ ---(tap)---(tap)---(tap)---> // ίʔυ button.rx.tap // rx.tap෦෼͕RxCocoaͷ֦ு෦෼ .subscribe { print("λοϓ͞Ε·ͨ͠") } .dispose()

Slide 25

Slide 25 text

ςΩετϑΟʔϧυετϦʔϜ จࣈ਺Λϥϕϧදࣔ

Slide 26

Slide 26 text

// Πϝʔδ ---͋---͍͋---͍͋͏---͍͋͏͑---͍͋͏͓͑---> // ίʔυ textField.rx.text.asObservable() // ೖྗςΩετΛετϦʔϜʹม׵ .map { String($0.count) } // จࣈྻΛจࣈ਺ʹม׵ .bindTo(label.rx.text) // ϥϕϧʹ൓ө .disposed(by: disposeBag)

Slide 27

Slide 27 text

ळͷຯ֮ετϦʔϜ

Slide 28

Slide 28 text

// Πϝʔδ --- ! --- " --- # --- ! --- " --- ! ---> filterͰͿͲ͏͚ͩͷετϦʔϜʹม׵͢ΔΑ --- ! --- ! --- ! ---> // ίʔυ Observable .from([" ! ", " " ", " # ", " ! ", " " ", " "]) // ഑ྻ͔ΒετϦʔϜΛੜ੒ .filter( $0 == " ! ") // ετϦʔϜΛม׵ .subscribe { print($0) } .dispose()

Slide 29

Slide 29 text

API௨৴ετϦʔϜ

Slide 30

Slide 30 text

// Πϝʔδ ---Ϩεϙϯε---|(completed)---> ΋͘͠͸ ---×(eror)---> // ίʔυ Observable.create { observer -> Disposable in // ؔ਺͔ΒετϦʔϜΛੜ੒ APIClient.request() { response in switch response.result { case .success: observer.onNext(response) // nextΛૹ৴ observer.onCompleted() // completedΛૹ৴ case .failure(let error): observer.onError(error) // errorΛૹ৴ } } return Disposables.create() }

Slide 31

Slide 31 text

΋͏গ࣮͠ફతʹ

Slide 32

Slide 32 text

ෳ਺APIΛ௚ྻʹϦΫΤετ

Slide 33

Slide 33 text

1. Ϙλϯλοϓ 2. loading...ͱදࣔ 3. Ϣʔβʔ৘ใAPIϦΫΤετʢϢʔβʔIDऔಘʣ 4. ϦϙδτϦҰཡAPIϦΫΤετʢϢʔβʔIDΛύϥϝʔλʔʹʣ 5. औಘͨ͠ϦϙδτϦҰཡͷλΠτϧΛදࣔ

Slide 34

Slide 34 text

RxͰͳ͍৔߹

Slide 35

Slide 35 text

@IBAction func buttonTapped() { label.text = "loading..." apiRequest() } func apiRequest() { userInfoModel.request { [weak self] result in switch result { case .success(let userInfo): self?.repositoryListModel.request(userId: userInfo.userId) { result in switch result { case .success(let repositoryList): self?.label.text = repositoryList.map { $0.title }.joined(separator: "\n") case .failure(let error): print(error) } } case .failure(let error): print(error) } } }

Slide 36

Slide 36 text

Rxͷ৔߹

Slide 37

Slide 37 text

☝ ؔ਺ͷ໭Γ஋ΛObservableʹ͓ͯ͘͠

Slide 38

Slide 38 text

func rxRequest(api: API) -> Observable { return Observable.create { observer -> Disposable in Alamofire.request(api.urlString, method: api.method, parameters: api.parameters) .validate(statusCode: 200..<300) .responseJSON { response in switch response.result { case .success: do { guard let jsonData = response.data else { observer.onError(APIClientError.emptyResponseError) return } let jsonDecoder = JSONDecoder() jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase let data = try jsonDecoder.decode(T.self, from: jsonData) observer.onNext(data) observer.onCompleted() } catch { observer.onError(APIClientError.parseError(error)) } case .failure(let error): observer.onError(APIClientError.connectionError(error)) } } return Disposables.create() } }

Slide 39

Slide 39 text

struct UserInfoModel { typealias ResponseType = UserInfo private let apiClient: APIClient init(apiClient: APIClient = APIClient()) { self.apiClient = apiClient } func request(completion: @escaping (Result) -> Void) { apiClient.request(api: .userInfo) { result in completion(result) } } func rxRequest() -> Observable { return apiClient.rxRequest(api: .userInfo) } }

Slide 40

Slide 40 text

☝ flatMapͰAPIΛ௚ྻʹ࿈݁ ʢ৽͍͠ετϦʔϜʹܨ͛Δʣ

Slide 41

Slide 41 text

rxRequestButton.rx.tap .do(onNext: { _ in self.label.text = "loading..." }) .flatMap { self.userInfoModel.rxRequest() } .flatMap { self.repositoryListModel.rxRequest(userId: $0.userId) } .map { $0.map { $0.title }.joined(separator: "\n") } .catchErrorJustReturn("Τϥʔ") .bind(to: self.label.rx.text) .disposed(by: disposeBag)

Slide 42

Slide 42 text

ඇಉظΠϕϯτΛந৅Խͯ͠એݴతʹॻ͚Δ

Slide 43

Slide 43 text

ϦΞΫςΟϒϓϩάϥϛϯάͷ Πϝʔδ͕༙͖·͔ͨ͠ʁ

Slide 44

Slide 44 text

try! RxSwift

Slide 45

Slide 45 text

ιʔείʔυ shtnkgm/RxSwiftPlaygroundSample cloneͯ͠Ϗϧυ͢Ε͹RxSwift͕PlaygroundͰ͙࣮͢ߦͰ͖·͢ shtnkgm/RxAPIRequest RxSwiftͰͷAPIϦΫΤεταϯϓϧ

Slide 46

Slide 46 text

ΑΓৄ͘͠ • Reactive Programming with Swift 4 • ReactiveX/RxSwift - Documentation

Slide 47

Slide 47 text

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