Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

658 views

Published on

tokushima.app #7で発表したスライド

Published in: Software
  • Be the first to comment

  • Be the first to like this

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

  1. 1. RxSwiftを バインディングツール として使ってみる 2016/07/24 tokushima.app #7 @hironytic
  2. 2. 自己紹介 • 一宮 浩教 • 徳島市内で働く
 iOS/Android/Windows(ストアアプリ) エンジニア • https://twitter.com/hironytic • https://github.com/hironytic
  3. 3. RxSwift • https://github.com/ReactiveX/RxSwift • http://reactivex.io/ • RxJavaとかRxJSとか、アレの仲間
  4. 4. 足し算アプリ • 数値入力欄 ×2 • 計算ボタン(=) • 数値入力欄を両方埋めると 計算ボタンが有効 • 計算ボタンを押すと入力さ れた2つの数値を足し算し た結果を表示
  5. 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. 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. 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. 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. 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
  10. 10. 🐣 なにがうれしいの?
  11. 11. MVVM View ViewModel Model 外観 プラットフォ ームの都合 UIの状態 UIのための ロジック ビジネス ロジック 呼び出し 通知 バイン ディング WPFなら XAML+コード ビハインド RxSwift
  12. 12. ViewModelのテスト • UIロジックのテスト • UIテストって面倒じゃないですか? - デザインの変更に弱いとか - Accessibility Identifierとか • ViewModelのテストでUIロジックはテストできる
  13. 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
  14. 14. まとめ 🚀バインディングだけのために、 RxSwiftを使うという選択肢も ありじゃないの? 📃ソースコード https://github.com/hironytic/RxSwiftBindingExample
  15. 15. 補足 • Rxのoperatorを使った一般的なやり方をdisってい るわけではありません!!🙇 - 個人的にはObservableの川は結構好きです • Rxのoperatorを使ってもViewModelは作れます - RxSwift本家のサンプル - 今回のViewModelを公開した入出力はそのままで、 Rxのoperatorを使うようにもできます
  16. 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 外に公開した 入出力
  17. 17. おしまい ありがとうございました

×