SlideShare une entreprise Scribd logo
1  sur  17
Télécharger pour lire hors ligne
RxSwiftを
バインディングツール
として使ってみる
2016/07/24
tokushima.app #7
@hironytic
自己紹介
• 一宮 浩教
• 徳島市内で働く

iOS/Android/Windows(ストアアプリ)
エンジニア
• https://twitter.com/hironytic
• https://github.com/hironytic
RxSwift
• https://github.com/ReactiveX/RxSwift
• http://reactivex.io/
• RxJavaとかRxJSとか、アレの仲間
足し算アプリ
• 数値入力欄 ×2
• 計算ボタン(=)
• 数値入力欄を両方埋めると
計算ボタンが有効
• 計算ボタンを押すと入力さ
れた2つの数値を足し算し
た結果を表示
RxSwiftっぽく
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let numbers = Observable.combineLatest(number1Field.rx_text,
number2Field.rx_text) { ($0, $1) }
numbers
.map { (number1, number2) in
return !number1.isEmpty && !number2.isEmpty
}
.bindTo(calcButton.rx_enabled)
.addDisposableTo(disposeBag)
calcButton.rx_tap
.withLatestFrom(numbers)
.map { (number1, number2) in
let n1 = Int(number1) ?? 0
let n2 = Int(number2) ?? 0
return String(n1 + n2)
}
.startWith("")
.bindTo(answerLabel.rx_text)
.addDisposableTo(disposeBag)
}
!今回の話では
忘れてください
ViewController
RxSwiftを使わないなら
override func viewDidLoad() {
super.viewDidLoad()
number1Field.text = ""
number2Field.text = ""
answerLabel.text = ""
calcButton.enabled = false
}
@IBAction private func calc(sender: AnyObject) {
let n1 = Int(number1Field.text ?? "") ?? 0
let n2 = Int(number2Field.text ?? "") ?? 0
answerLabel.text = String(n1 + n2)
}
@IBAction func number1Changed(sender: AnyObject) {
updateCalcState()
}
@IBAction func number2Changed(sender: AnyObject) {
updateCalcState()
}
private func updateCalcState() {
calcButton.enabled = !(number1Field.text?.isEmpty ?? true)
&& !(number2Field.text?.isEmpty ?? true)
}
ViewController
計算ボタンの

タップ
数値
入力時
今回のやり方
var disposeBag = DisposeBag()
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.number1Text
.bindTo(number1Field.rx_text)
.addDisposableTo(disposeBag)
viewModel.number2Text
.bindTo(number2Field.rx_text)
.addDisposableTo(disposeBag)
viewModel.calcEnabled
.bindTo(calcButton.rx_enabled)
.addDisposableTo(disposeBag)
viewModel.answerText
.bindTo(answerLabel.rx_text)
.addDisposableTo(disposeBag)
number1Field.rx_text
.bindTo(viewModel.number1ChangedAction)
.addDisposableTo(disposeBag)
number2Field.rx_text
.bindTo(viewModel.number2ChangedAction)
.addDisposableTo(disposeBag)
calcButton.rx_tap
.bindTo(viewModel.calcAction)
.addDisposableTo(disposeBag)
}
ViewControllerViewModel
に分割
ViewModelの
出力を
バインド
ViewModelの
入力へ
バインド
今回のやり方
let number1Text: Observable<String>
let number2Text: Observable<String>
let calcEnabled: Observable<Bool>
let answerText: Observable<String>
let number1ChangedAction: AnyObserver<String>
let number2ChangedAction: AnyObserver<String>
let calcAction: AnyObserver<Void>
private let _number1Text = Variable<String>("")
private let _number2Text = Variable<String>("")
private let _calcEnabled = Variable<Bool>(false)
private let _answerText = Variable<String>("")
private let _calcAction = ActionObserver<Void>()
private let _number1ChangedAction = ActionObserver<String>()
private let _number2ChangedAction = ActionObserver<String>()
init() {
number1Text = _number1Text.asObservable()
number2Text = _number2Text.asObservable()
answerText = _answerText.asObservable()
calcEnabled = _calcEnabled.asObservable()
number1ChangedAction = _number1ChangedAction.asObserver()
number2ChangedAction = _number2ChangedAction.asObserver()
calcAction = _calcAction.asObserver()
_calcAction.handler = { [weak self] in self?.calc() }
_number1ChangedAction.handler = { [weak self] in self?.number1Changed($0) }
_number2ChangedAction.handler = { [weak self] in self?.number2Changed($0) }
}
ViewModel
外に公開した
入出力
状態の保持
今回のやり方
private func calc() {
let n1 = Int(_number1Text.value) ?? 0
let n2 = Int(_number2Text.value) ?? 0
_answerText.value = String(n1 + n2)
}
private func number1Changed(value: String) {
_number1Text.value = value
updateCalcState()
}
private func number2Changed(value: String) {
_number2Text.value = value
updateCalcState()
}
private func updateCalcState() {
_calcEnabled.value = !_number1Text.value.isEmpty
&& !_number2Text.value.isEmpty
}
ViewModel
🐣
なにがうれしいの?
MVVM
View ViewModel Model
外観
プラットフォ
ームの都合
UIの状態
UIのための
ロジック
ビジネス
ロジック
呼び出し
通知
バイン
ディング
WPFなら
XAML+コード
ビハインド
RxSwift
ViewModelのテスト
• UIロジックのテスト
• UIテストって面倒じゃないですか?
- デザインの変更に弱いとか
- Accessibility Identifierとか
• ViewModelのテストでUIロジックはテストできる
ViewModelのテスト
func testCalc() {
let disposeBag = DisposeBag()
let viewModel = ViewModel()
let answerObserver = FulfillObserver(
expectationWithDescription("empty before calculation")) { $0 == "" }
viewModel.answerText
.bindTo(answerObserver)
.addDisposableTo(disposeBag)
waitForExpectationsWithTimeout(1.0, handler: nil)
answerObserver.reset(
expectationWithDescription("30 after calculation")) { $0 == "30" }
viewModel.number1ChangedAction.onNext("10")
viewModel.number2ChangedAction.onNext("20")
viewModel.calcAction.onNext()
waitForExpectationsWithTimeout(1.0, handler: nil)
}
Test
まとめ
🚀バインディングだけのために、
RxSwiftを使うという選択肢も
ありじゃないの?
📃ソースコード
https://github.com/hironytic/RxSwiftBindingExample
補足
• Rxのoperatorを使った一般的なやり方をdisってい
るわけではありません!!🙇
- 個人的にはObservableの川は結構好きです
• Rxのoperatorを使ってもViewModelは作れます
- RxSwift本家のサンプル
- 今回のViewModelを公開した入出力はそのままで、
Rxのoperatorを使うようにもできます
ViewModel改
let number1Text: Observable<String>
let number2Text: Observable<String>
let calcEnabled: Observable<Bool>
let answerText: Observable<String>
let number1ChangedAction: AnyObserver<String>
let number2ChangedAction: AnyObserver<String>
let calcAction: AnyObserver<Void>
private let _number1Text = BehaviorSubject<String>(value: "")
private let _number2Text = BehaviorSubject<String>(value: "")
private let _calcAction = PublishSubject<Void>()
init() {
number1Text = _number1Text.asObservable()
number2Text = _number2Text.asObservable()
number1ChangedAction = _number1Text.asObserver()
number2ChangedAction = _number2Text.asObserver()
calcAction = _calcAction.asObserver()
let numbers = Observable.combineLatest(number1Text, number2Text) { ($0, $1) }
calcEnabled = numbers
.map { (number1, number2) in
return !number1.isEmpty && !number2.isEmpty
}
answerText = _calcAction
.withLatestFrom(numbers)
.map { (number1, number2) in
let n1 = Int(number1) ?? 0
let n2 = Int(number2) ?? 0
return String(n1 + n2)
}
.startWith("")
}
ViewModel
外に公開した
入出力
おしまい
ありがとうございました

Contenu connexe

En vedette

RxSwift コードリーディングの勘所@社内RxSwift勉強会
RxSwift コードリーディングの勘所@社内RxSwift勉強会RxSwift コードリーディングの勘所@社内RxSwift勉強会
RxSwift コードリーディングの勘所@社内RxSwift勉強会Yuki Takahashi
 
RxSwiftのデータバインディングだけ
RxSwiftのデータバインディングだけRxSwiftのデータバインディングだけ
RxSwiftのデータバインディングだけHironytic
 
今日こそ理解するHot変換
今日こそ理解するHot変換今日こそ理解するHot変換
今日こそ理解するHot変換Yuki Takahashi
 
iOS開発 本当にあった怖い話
iOS開発 本当にあった怖い話iOS開発 本当にあった怖い話
iOS開発 本当にあった怖い話Kazuhiro Sakamoto
 
マルチスレッドRxSwift @ 社内RxSwift勉強会
マルチスレッドRxSwift @ 社内RxSwift勉強会マルチスレッドRxSwift @ 社内RxSwift勉強会
マルチスレッドRxSwift @ 社内RxSwift勉強会Yuki Takahashi
 
What is reactive programming?
What is reactive programming?What is reactive programming?
What is reactive programming?Kenji Tanaka
 
Java → Kotlin 変換 そのあとに。
Java → Kotlin 変換 そのあとに。Java → Kotlin 変換 そのあとに。
Java → Kotlin 変換 そのあとに。健一 辰濱
 
コンポーネント単位で考えるWeb制作
コンポーネント単位で考えるWeb制作コンポーネント単位で考えるWeb制作
コンポーネント単位で考えるWeb制作祐磨 堀
 
Apakah saudara sudah tahu politikituapa
Apakah saudara sudah tahu politikituapaApakah saudara sudah tahu politikituapa
Apakah saudara sudah tahu politikituapahenry jaya teddy
 
Quitters, climbers, campers
Quitters, climbers, campersQuitters, climbers, campers
Quitters, climbers, campershenry jaya teddy
 
Allah isa almasih, muslim, bani israel sama
Allah isa almasih, muslim, bani israel samaAllah isa almasih, muslim, bani israel sama
Allah isa almasih, muslim, bani israel samahenry jaya teddy
 
Apakah saudara sudah tahu sn dan ss
Apakah saudara sudah tahu sn dan ssApakah saudara sudah tahu sn dan ss
Apakah saudara sudah tahu sn dan sshenry jaya teddy
 
Продвижение в инстаграм на свадебном рынке
Продвижение в инстаграм на свадебном рынкеПродвижение в инстаграм на свадебном рынке
Продвижение в инстаграм на свадебном рынкеDaria Manelova
 
Medical Revelation Of India
Medical Revelation Of IndiaMedical Revelation Of India
Medical Revelation Of Indiakiran karanjkar
 
Apakah saudara sudah tahu ump jakarta 3,1 juta rupiah
Apakah saudara sudah tahu ump jakarta 3,1 juta rupiahApakah saudara sudah tahu ump jakarta 3,1 juta rupiah
Apakah saudara sudah tahu ump jakarta 3,1 juta rupiahhenry jaya teddy
 
Apakah saudara sudah tahu kelakukanpenulisblogislam
Apakah saudara sudah tahu kelakukanpenulisblogislamApakah saudara sudah tahu kelakukanpenulisblogislam
Apakah saudara sudah tahu kelakukanpenulisblogislamhenry jaya teddy
 

En vedette (20)

RxSwift コードリーディングの勘所@社内RxSwift勉強会
RxSwift コードリーディングの勘所@社内RxSwift勉強会RxSwift コードリーディングの勘所@社内RxSwift勉強会
RxSwift コードリーディングの勘所@社内RxSwift勉強会
 
RxSwiftのデータバインディングだけ
RxSwiftのデータバインディングだけRxSwiftのデータバインディングだけ
RxSwiftのデータバインディングだけ
 
今日こそ理解するHot変換
今日こそ理解するHot変換今日こそ理解するHot変換
今日こそ理解するHot変換
 
RxSwift x APIKit
RxSwift x APIKitRxSwift x APIKit
RxSwift x APIKit
 
MVVM & RxSwift
MVVM & RxSwiftMVVM & RxSwift
MVVM & RxSwift
 
iOS開発 本当にあった怖い話
iOS開発 本当にあった怖い話iOS開発 本当にあった怖い話
iOS開発 本当にあった怖い話
 
マルチスレッドRxSwift @ 社内RxSwift勉強会
マルチスレッドRxSwift @ 社内RxSwift勉強会マルチスレッドRxSwift @ 社内RxSwift勉強会
マルチスレッドRxSwift @ 社内RxSwift勉強会
 
RxSwift x Realm
RxSwift x RealmRxSwift x Realm
RxSwift x Realm
 
What is reactive programming?
What is reactive programming?What is reactive programming?
What is reactive programming?
 
Java → Kotlin 変換 そのあとに。
Java → Kotlin 変換 そのあとに。Java → Kotlin 変換 そのあとに。
Java → Kotlin 変換 そのあとに。
 
コンポーネント単位で考えるWeb制作
コンポーネント単位で考えるWeb制作コンポーネント単位で考えるWeb制作
コンポーネント単位で考えるWeb制作
 
Apakah saudara sudah tahu politikituapa
Apakah saudara sudah tahu politikituapaApakah saudara sudah tahu politikituapa
Apakah saudara sudah tahu politikituapa
 
Quitters, climbers, campers
Quitters, climbers, campersQuitters, climbers, campers
Quitters, climbers, campers
 
Allah isa almasih, muslim, bani israel sama
Allah isa almasih, muslim, bani israel samaAllah isa almasih, muslim, bani israel sama
Allah isa almasih, muslim, bani israel sama
 
Apakah saudara sudah tahu sn dan ss
Apakah saudara sudah tahu sn dan ssApakah saudara sudah tahu sn dan ss
Apakah saudara sudah tahu sn dan ss
 
Продвижение в инстаграм на свадебном рынке
Продвижение в инстаграм на свадебном рынкеПродвижение в инстаграм на свадебном рынке
Продвижение в инстаграм на свадебном рынке
 
Medical Revelation Of India
Medical Revelation Of IndiaMedical Revelation Of India
Medical Revelation Of India
 
Heart transformation
Heart transformationHeart transformation
Heart transformation
 
Apakah saudara sudah tahu ump jakarta 3,1 juta rupiah
Apakah saudara sudah tahu ump jakarta 3,1 juta rupiahApakah saudara sudah tahu ump jakarta 3,1 juta rupiah
Apakah saudara sudah tahu ump jakarta 3,1 juta rupiah
 
Apakah saudara sudah tahu kelakukanpenulisblogislam
Apakah saudara sudah tahu kelakukanpenulisblogislamApakah saudara sudah tahu kelakukanpenulisblogislam
Apakah saudara sudah tahu kelakukanpenulisblogislam
 

Similaire à RxSwiftをバインディングツールとして使ってみる

Sansan様 登壇資料
Sansan様 登壇資料Sansan様 登壇資料
Sansan様 登壇資料Daisuke Nagata
 
Why Reactive Matters #ScalaMatsuri
Why Reactive Matters #ScalaMatsuriWhy Reactive Matters #ScalaMatsuri
Why Reactive Matters #ScalaMatsuriYuta Okamoto
 
Node.jsでつくるNode.js ミニインタープリター&コンパイラー
Node.jsでつくるNode.js ミニインタープリター&コンパイラーNode.jsでつくるNode.js ミニインタープリター&コンパイラー
Node.jsでつくるNode.js ミニインタープリター&コンパイラーmganeko
 
10分で分かるr言語入門ver2.10 14 1101
10分で分かるr言語入門ver2.10 14 110110分で分かるr言語入門ver2.10 14 1101
10分で分かるr言語入門ver2.10 14 1101Nobuaki Oshiro
 
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Yoshifumi Kawai
 
Swift 2.0 大域関数の行方から #swift2symposium
Swift 2.0 大域関数の行方から #swift2symposiumSwift 2.0 大域関数の行方から #swift2symposium
Swift 2.0 大域関数の行方から #swift2symposiumTomohiro Kumagai
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competitionyak1ex
 
プログラミング言語Scala
プログラミング言語Scalaプログラミング言語Scala
プログラミング言語ScalaTanUkkii
 
怠惰なRubyistへの道 fukuoka rubykaigi01
怠惰なRubyistへの道 fukuoka rubykaigi01怠惰なRubyistへの道 fukuoka rubykaigi01
怠惰なRubyistへの道 fukuoka rubykaigi01nagachika t
 
実務者のためのかんたんScalaz
実務者のためのかんたんScalaz実務者のためのかんたんScalaz
実務者のためのかんたんScalazTomoharu ASAMI
 
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8y_taka_23
 
ApexトリガのBest Practiceを目指して
ApexトリガのBest Practiceを目指してApexトリガのBest Practiceを目指して
ApexトリガのBest Practiceを目指してTakahiro Yonei
 
24時間でiOSアプリ-Twitterクライアント-の作成にチャレンジ ver1.1
24時間でiOSアプリ-Twitterクライアント-の作成にチャレンジ ver1.124時間でiOSアプリ-Twitterクライアント-の作成にチャレンジ ver1.1
24時間でiOSアプリ-Twitterクライアント-の作成にチャレンジ ver1.1聡 中川
 
10分で分かるr言語入門ver2.9 14 0920
10分で分かるr言語入門ver2.9 14 0920 10分で分かるr言語入門ver2.9 14 0920
10分で分かるr言語入門ver2.9 14 0920 Nobuaki Oshiro
 
Replace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JPReplace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JPAkira Takahashi
 
Swift - Result&lt;t>型で結果を返すのは邪道か,王道か
Swift - Result&lt;t>型で結果を返すのは邪道か,王道かSwift - Result&lt;t>型で結果を返すのは邪道か,王道か
Swift - Result&lt;t>型で結果を返すのは邪道か,王道かYuichi Yoshida
 
Clojure programming-chapter-2
Clojure programming-chapter-2Clojure programming-chapter-2
Clojure programming-chapter-2Masao Kato
 

Similaire à RxSwiftをバインディングツールとして使ってみる (20)

たのしい関数型
たのしい関数型たのしい関数型
たのしい関数型
 
Sansan様 登壇資料
Sansan様 登壇資料Sansan様 登壇資料
Sansan様 登壇資料
 
Why Reactive Matters #ScalaMatsuri
Why Reactive Matters #ScalaMatsuriWhy Reactive Matters #ScalaMatsuri
Why Reactive Matters #ScalaMatsuri
 
Node.jsでつくるNode.js ミニインタープリター&コンパイラー
Node.jsでつくるNode.js ミニインタープリター&コンパイラーNode.jsでつくるNode.js ミニインタープリター&コンパイラー
Node.jsでつくるNode.js ミニインタープリター&コンパイラー
 
10分で分かるr言語入門ver2.10 14 1101
10分で分かるr言語入門ver2.10 14 110110分で分かるr言語入門ver2.10 14 1101
10分で分かるr言語入門ver2.10 14 1101
 
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
 
Swift 2.0 大域関数の行方から #swift2symposium
Swift 2.0 大域関数の行方から #swift2symposiumSwift 2.0 大域関数の行方から #swift2symposium
Swift 2.0 大域関数の行方から #swift2symposium
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
 
プログラミング言語Scala
プログラミング言語Scalaプログラミング言語Scala
プログラミング言語Scala
 
Swiftyを試す
Swiftyを試すSwiftyを試す
Swiftyを試す
 
怠惰なRubyistへの道 fukuoka rubykaigi01
怠惰なRubyistへの道 fukuoka rubykaigi01怠惰なRubyistへの道 fukuoka rubykaigi01
怠惰なRubyistへの道 fukuoka rubykaigi01
 
実務者のためのかんたんScalaz
実務者のためのかんたんScalaz実務者のためのかんたんScalaz
実務者のためのかんたんScalaz
 
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
 
ApexトリガのBest Practiceを目指して
ApexトリガのBest Practiceを目指してApexトリガのBest Practiceを目指して
ApexトリガのBest Practiceを目指して
 
24時間でiOSアプリ-Twitterクライアント-の作成にチャレンジ ver1.1
24時間でiOSアプリ-Twitterクライアント-の作成にチャレンジ ver1.124時間でiOSアプリ-Twitterクライアント-の作成にチャレンジ ver1.1
24時間でiOSアプリ-Twitterクライアント-の作成にチャレンジ ver1.1
 
10分で分かるr言語入門ver2.9 14 0920
10分で分かるr言語入門ver2.9 14 0920 10分で分かるr言語入門ver2.9 14 0920
10分で分かるr言語入門ver2.9 14 0920
 
Replace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JPReplace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JP
 
Visualforce + jQuery
Visualforce + jQueryVisualforce + jQuery
Visualforce + jQuery
 
Swift - Result&lt;t>型で結果を返すのは邪道か,王道か
Swift - Result&lt;t>型で結果を返すのは邪道か,王道かSwift - Result&lt;t>型で結果を返すのは邪道か,王道か
Swift - Result&lt;t>型で結果を返すのは邪道か,王道か
 
Clojure programming-chapter-2
Clojure programming-chapter-2Clojure programming-chapter-2
Clojure programming-chapter-2
 

Plus de Hironytic

DroidKaigi 2018報告会(公式アプリへのコントリビュート)
DroidKaigi 2018報告会(公式アプリへのコントリビュート)DroidKaigi 2018報告会(公式アプリへのコントリビュート)
DroidKaigi 2018報告会(公式アプリへのコントリビュート)Hironytic
 
DroidKaigi 2018報告会(はじめてのKotlinハンズオン)
DroidKaigi 2018報告会(はじめてのKotlinハンズオン)DroidKaigi 2018報告会(はじめてのKotlinハンズオン)
DroidKaigi 2018報告会(はじめてのKotlinハンズオン)Hironytic
 
DroidKaigi 2018報告会(会場の風景)
DroidKaigi 2018報告会(会場の風景)DroidKaigi 2018報告会(会場の風景)
DroidKaigi 2018報告会(会場の風景)Hironytic
 
Firebaseの新しいデータベース
Firebaseの新しいデータベースFirebaseの新しいデータベース
Firebaseの新しいデータベースHironytic
 
CocoaPodsのはなし
CocoaPodsのはなしCocoaPodsのはなし
CocoaPodsのはなしHironytic
 
Heroku+MongoLabでダミーサーバー
Heroku+MongoLabでダミーサーバーHeroku+MongoLabでダミーサーバー
Heroku+MongoLabでダミーサーバーHironytic
 

Plus de Hironytic (6)

DroidKaigi 2018報告会(公式アプリへのコントリビュート)
DroidKaigi 2018報告会(公式アプリへのコントリビュート)DroidKaigi 2018報告会(公式アプリへのコントリビュート)
DroidKaigi 2018報告会(公式アプリへのコントリビュート)
 
DroidKaigi 2018報告会(はじめてのKotlinハンズオン)
DroidKaigi 2018報告会(はじめてのKotlinハンズオン)DroidKaigi 2018報告会(はじめてのKotlinハンズオン)
DroidKaigi 2018報告会(はじめてのKotlinハンズオン)
 
DroidKaigi 2018報告会(会場の風景)
DroidKaigi 2018報告会(会場の風景)DroidKaigi 2018報告会(会場の風景)
DroidKaigi 2018報告会(会場の風景)
 
Firebaseの新しいデータベース
Firebaseの新しいデータベースFirebaseの新しいデータベース
Firebaseの新しいデータベース
 
CocoaPodsのはなし
CocoaPodsのはなしCocoaPodsのはなし
CocoaPodsのはなし
 
Heroku+MongoLabでダミーサーバー
Heroku+MongoLabでダミーサーバーHeroku+MongoLabでダミーサーバー
Heroku+MongoLabでダミーサーバー
 

RxSwiftをバインディングツールとして使ってみる

  • 2. 自己紹介 • 一宮 浩教 • 徳島市内で働く
 iOS/Android/Windows(ストアアプリ) エンジニア • https://twitter.com/hironytic • https://github.com/hironytic
  • 4. 足し算アプリ • 数値入力欄 ×2 • 計算ボタン(=) • 数値入力欄を両方埋めると 計算ボタンが有効 • 計算ボタンを押すと入力さ れた2つの数値を足し算し た結果を表示
  • 5. RxSwiftっぽく var disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() let numbers = Observable.combineLatest(number1Field.rx_text, number2Field.rx_text) { ($0, $1) } numbers .map { (number1, number2) in return !number1.isEmpty && !number2.isEmpty } .bindTo(calcButton.rx_enabled) .addDisposableTo(disposeBag) calcButton.rx_tap .withLatestFrom(numbers) .map { (number1, number2) in let n1 = Int(number1) ?? 0 let n2 = Int(number2) ?? 0 return String(n1 + n2) } .startWith("") .bindTo(answerLabel.rx_text) .addDisposableTo(disposeBag) } !今回の話では 忘れてください ViewController
  • 6. RxSwiftを使わないなら override func viewDidLoad() { super.viewDidLoad() number1Field.text = "" number2Field.text = "" answerLabel.text = "" calcButton.enabled = false } @IBAction private func calc(sender: AnyObject) { let n1 = Int(number1Field.text ?? "") ?? 0 let n2 = Int(number2Field.text ?? "") ?? 0 answerLabel.text = String(n1 + n2) } @IBAction func number1Changed(sender: AnyObject) { updateCalcState() } @IBAction func number2Changed(sender: AnyObject) { updateCalcState() } private func updateCalcState() { calcButton.enabled = !(number1Field.text?.isEmpty ?? true) && !(number2Field.text?.isEmpty ?? true) } ViewController 計算ボタンの
 タップ 数値 入力時
  • 7. 今回のやり方 var disposeBag = DisposeBag() let viewModel = ViewModel() override func viewDidLoad() { super.viewDidLoad() viewModel.number1Text .bindTo(number1Field.rx_text) .addDisposableTo(disposeBag) viewModel.number2Text .bindTo(number2Field.rx_text) .addDisposableTo(disposeBag) viewModel.calcEnabled .bindTo(calcButton.rx_enabled) .addDisposableTo(disposeBag) viewModel.answerText .bindTo(answerLabel.rx_text) .addDisposableTo(disposeBag) number1Field.rx_text .bindTo(viewModel.number1ChangedAction) .addDisposableTo(disposeBag) number2Field.rx_text .bindTo(viewModel.number2ChangedAction) .addDisposableTo(disposeBag) calcButton.rx_tap .bindTo(viewModel.calcAction) .addDisposableTo(disposeBag) } ViewControllerViewModel に分割 ViewModelの 出力を バインド ViewModelの 入力へ バインド
  • 8. 今回のやり方 let number1Text: Observable<String> let number2Text: Observable<String> let calcEnabled: Observable<Bool> let answerText: Observable<String> let number1ChangedAction: AnyObserver<String> let number2ChangedAction: AnyObserver<String> let calcAction: AnyObserver<Void> private let _number1Text = Variable<String>("") private let _number2Text = Variable<String>("") private let _calcEnabled = Variable<Bool>(false) private let _answerText = Variable<String>("") private let _calcAction = ActionObserver<Void>() private let _number1ChangedAction = ActionObserver<String>() private let _number2ChangedAction = ActionObserver<String>() init() { number1Text = _number1Text.asObservable() number2Text = _number2Text.asObservable() answerText = _answerText.asObservable() calcEnabled = _calcEnabled.asObservable() number1ChangedAction = _number1ChangedAction.asObserver() number2ChangedAction = _number2ChangedAction.asObserver() calcAction = _calcAction.asObserver() _calcAction.handler = { [weak self] in self?.calc() } _number1ChangedAction.handler = { [weak self] in self?.number1Changed($0) } _number2ChangedAction.handler = { [weak self] in self?.number2Changed($0) } } ViewModel 外に公開した 入出力 状態の保持
  • 9. 今回のやり方 private func calc() { let n1 = Int(_number1Text.value) ?? 0 let n2 = Int(_number2Text.value) ?? 0 _answerText.value = String(n1 + n2) } private func number1Changed(value: String) { _number1Text.value = value updateCalcState() } private func number2Changed(value: String) { _number2Text.value = value updateCalcState() } private func updateCalcState() { _calcEnabled.value = !_number1Text.value.isEmpty && !_number2Text.value.isEmpty } ViewModel
  • 12. ViewModelのテスト • UIロジックのテスト • UIテストって面倒じゃないですか? - デザインの変更に弱いとか - Accessibility Identifierとか • ViewModelのテストでUIロジックはテストできる
  • 13. ViewModelのテスト func testCalc() { let disposeBag = DisposeBag() let viewModel = ViewModel() let answerObserver = FulfillObserver( expectationWithDescription("empty before calculation")) { $0 == "" } viewModel.answerText .bindTo(answerObserver) .addDisposableTo(disposeBag) waitForExpectationsWithTimeout(1.0, handler: nil) answerObserver.reset( expectationWithDescription("30 after calculation")) { $0 == "30" } viewModel.number1ChangedAction.onNext("10") viewModel.number2ChangedAction.onNext("20") viewModel.calcAction.onNext() waitForExpectationsWithTimeout(1.0, handler: nil) } Test
  • 15. 補足 • Rxのoperatorを使った一般的なやり方をdisってい るわけではありません!!🙇 - 個人的にはObservableの川は結構好きです • Rxのoperatorを使ってもViewModelは作れます - RxSwift本家のサンプル - 今回のViewModelを公開した入出力はそのままで、 Rxのoperatorを使うようにもできます
  • 16. ViewModel改 let number1Text: Observable<String> let number2Text: Observable<String> let calcEnabled: Observable<Bool> let answerText: Observable<String> let number1ChangedAction: AnyObserver<String> let number2ChangedAction: AnyObserver<String> let calcAction: AnyObserver<Void> private let _number1Text = BehaviorSubject<String>(value: "") private let _number2Text = BehaviorSubject<String>(value: "") private let _calcAction = PublishSubject<Void>() init() { number1Text = _number1Text.asObservable() number2Text = _number2Text.asObservable() number1ChangedAction = _number1Text.asObserver() number2ChangedAction = _number2Text.asObserver() calcAction = _calcAction.asObserver() let numbers = Observable.combineLatest(number1Text, number2Text) { ($0, $1) } calcEnabled = numbers .map { (number1, number2) in return !number1.isEmpty && !number2.isEmpty } answerText = _calcAction .withLatestFrom(numbers) .map { (number1, number2) in let n1 = Int(number1) ?? 0 let n2 = Int(number2) ?? 0 return String(n1 + n2) } .startWith("") } ViewModel 外に公開した 入出力