Slide 1

Slide 1 text

RxSwiftでMVVMパターン 1 @Web技術勉強会#02 2019/06/08(土) @tofu_san0000

Slide 2

Slide 2 text

2 @tofu_san0000 豆腐: 木綿派 エディタ: XCode、VSCode、Vim、PHPStorm 言語: Swift、PHP、Vue.js

Slide 3

Slide 3 text

● RxSwift概要 ● MVVMパターン概要 ● ViewModel作成 ● ViewController作成 ● まとめ 3 アジェンダ

Slide 4

Slide 4 text

● RxSwift概要 ● MVVMパターン概要 ● ViewModel作成 ● ViewController作成 ● まとめ 4 アジェンダ

Slide 5

Slide 5 text

● 各言語で実装されている「Rx」のSwift向けライブラリ ● RxKotlin、RxJava、RxJS、RxPHPなどがある ● Rx ○ Reactive Programming ○ Extensions 5 RxSwift概要

Slide 6

Slide 6 text

● Observable ● Disposable ● Operator ● Scheduler ● etc ... 6 RxSwift概要>メソッド

Slide 7

Slide 7 text

● 監視することができる対象 ● 監視してデータ(イベント)を受け取ることができる ● Observableはストリーム(Stream)とも呼ぶ 7 RxSwift概要>メソッド>Observable Observable.of(“a”, “b”, “c”) .subscribe( // 処理 )

Slide 8

Slide 8 text

● next ○ データが正しく流れてきたというイベント ● error ○ エラー(例外)で異常停止した時に流れるイベント ● completed ○ 完了時に発生するイベント 8 RxSwift概要>メソッド>Observable>イベント next next next next error completed

Slide 9

Slide 9 text

9 RxSwift概要>メソッド>Observable>イベント Observable.of(“a”, “b”, “c”) .subscribe( x in // “a”, “b”, “c” onNext: { value in // イベント発生時の処理 }, onError: { error in // エラー発生時の処理 }, onCompleted: { // 完了時の処理 } )

Slide 10

Slide 10 text

● 直訳すると破棄・処分できるもの ● Observableをsubscribe(購読)した戻り値 ● Disposableに対してdispose()を呼ぶことで 非同期処理のキャンセル ● 監視する側としての処理をキャンセル 10 RxSwift概要>メソッド>DisposeBag

Slide 11

Slide 11 text

let disposeBag = DisposeBag() Observable.of(“a”, “b”, “c”) .subscribe( x in // “a”, “b”, “c” onNext: { value in // イベント発生時の処理 }, onError: { error in // エラー発生時の処理 }, onCompleted: { // 完了時の処理 } ).disposed(by: disposeBag) 11 RxSwift概要>メソッド>DisposeBag

Slide 12

Slide 12 text

● Observable、Subjectで作成したストリームを操作できる ● ストリームを変換・合成・生成 12 RxSwift概要>メソッド>Operator

Slide 13

Slide 13 text

let disposeBag = DisposeBag() Observable.of(1, 2, 3) .filter { x in > 1 } // 2, 3 .map { x in x * 2 } // 4, 6 .flatMap { x in Observable.of(x, x * 2) } // 4, 8, 6, 12 .subscribe( x in onNext: { value in // イベント発生時の処理 }, onError: { error in // エラー発生時の処理 }, onCompleted: { // 完了時の処理 } ) 13 RxSwift概要>メソッド>Operator

Slide 14

Slide 14 text

● 処理をどのスレッドで実行するか指定 14 RxSwift概要>メソッド>Scheduler

Slide 15

Slide 15 text

● RxSwift概要 ● MVVMパターン概要 ● ViewModel作成 ● ViewController作成 ● まとめ 15 アジェンダ

Slide 16

Slide 16 text

16 MVVMパターン概要 View Controller View Model Event Observable

Slide 17

Slide 17 text

17 MVVMパターン概要>例

Slide 18

Slide 18 text

LoginButtonの 有効・無効 18 MVVMパターン概要>例>View Model概要 View Model Input Output Email入力 Password入力 LoginButtonタップ Emailバリデーション Password バリデーション ログイン結果

Slide 19

Slide 19 text

● RxSwift概要 ● MVVMパターン概要 ● ViewModel作成 ● ViewController作成 ● まとめ 19 アジェンダ

Slide 20

Slide 20 text

20 View Model作成 class LoginViewModel {}

Slide 21

Slide 21 text

class LoginViewModel { // output lazy var validEmail: Observable lazy var validPassword: Observable lazy var loginButtonEnable: Observable lazy var login: Observable } 21 View Model作成>output定義

Slide 22

Slide 22 text

class LoginViewModel { // output // 省略 // input init( email: Observable, password: Observable, loginTap: Observable, loginAPI: LoginAPI.Type, disposeBag: DisposeBag ) {} } 22 View Model作成>input定義

Slide 23

Slide 23 text

class LoginViewModel { // output // 省略 // input init( email: Observable, password: Observable, loginTap: Observable, loginAPI: LoginAPI.Type, disposeBag: DisposeBag ) { validEmail = email.map { !$0.isEmpty } } } 23 View Model作成>validEmail

Slide 24

Slide 24 text

class LoginViewModel { // output // 省略 // input init( email: Observable, password: Observable, loginTap: Observable, loginAPI: LoginAPI.Type, disposeBag: DisposeBag ) { validEmail = email.map { !$0.isEmpty } validPassword = password.map { !$0.isEmpty } } } 24 View Model作成>validPassword

Slide 25

Slide 25 text

class LoginViewModel { // output    // 省略 // input init( // 省略 ) { // 省略 loginButtonEnable = Observable.combineLatest { validEmail, validPassword } .map { $0 && $1 } } } 25 View Model作成>loginButtonEnable

Slide 26

Slide 26 text

class LoginViewModel { // output    // 省略 // input init( // 省略 ) { // 省略 let params = Driver.combineLatest(email, password) { (email: $0, password: $1) } login = loginButtonTap. .withLatestFrom(params) .flatMap { loginAPI.login(with: params) } } 26 View Model作成>loginButtonEnable

Slide 27

Slide 27 text

class LoginViewModel { // output lazy var validEmail: Observable lazy var validPassword: Observable lazy var loginButtonEnable: Observable lazy var login: Observable // input init( email: Observable, password: Observable, loginTap: Observable, loginAPI: LoginAPI.Type, disposeBag: DisposeBag ) { validEmail = email.map { $0.isEmpty } validPassword = password.map { $0.isEmpty } loginButtonEnable = Observable.combineLatest { validEmail, validPassword } .map { $0 && $1 } let params = Driver.combineLatest(email, password) { (email: $0, password: $1) } login = loginBittonTap .withLatestFrom(params) .flatMap { loginAPI.login(with: params) } 27 View Model作成>完成

Slide 28

Slide 28 text

● RxSwift概要 ● MVVMパターン概要 ● ViewModel作成 ● ViewController作成 ● まとめ 28 アジェンダ

Slide 29

Slide 29 text

class LoginViewController: UIViewController { @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var emailErrorLabel: UILabel! @IBOutlet weak var passwordTextField: UITextField @IBOutlet weak var passwordErrorLabel: UILabel! @IBOutlet weak var loginButton: UIButton! } 29 View Controllerl作成

Slide 30

Slide 30 text

class LoginViewController: UIViewController { // 省略 private let disposeBag = DisposeBag() private let vm = LoginViewModel! override func viewDidLoad() { vm = LoginViewModel ( email: emailTextField.rx.text.orEmpty.asObservable, password: passwordTextField.rx.text.orEmpty.asObservable, loginAPI: LoginAPI.self, disposeBag: disposeBag ) } } 30 View Controllerl作成>LoginViewModel作成

Slide 31

Slide 31 text

class LoginViewController: UIViewController { // 省略 override func viewDidLoad() { vm = LoginViewModel ( email: emailTextField.rx.text.orEmpty.asObservable, password: passwordTextField.rx.text.orEmpty.asObservable, loginAPI: LoginAPI.self, disposeBag: disposeBag ) vm.validEmail .bind(to: emailErrorLabel.rx.isHidden) .disposed(by: disposeBag) } } 31 View Controller作成>validEmail

Slide 32

Slide 32 text

class LoginViewController: UIViewController { // 省略 override func viewDidLoad() { vm = LoginViewModel ( // 省略 ) vm.validEmail .bind(to: emailErrorLabel.rx.isHidden) .disposed(by: disposeBag) vm.validPassword .bind(to: passwordErrorLabel.rx.isHidden) .disposed(by: disposeBag) } 32 View Controllerl作成>validPassword

Slide 33

Slide 33 text

class LoginViewModel: UIViewController { // 省略 override func viewDidLoad() { vm = LoginViewModel ( // 省略 ) // 省略 vm.loginButtonEnable .bind(to: loginButton.rx.isEnable) .disposed(by: disposeBag) } } 33 View Controllerl作成>loginButtonEnable

Slide 34

Slide 34 text

class LoginViewModel: UIViewController { // 省略 override func viewDidLoad() { // 省略 vm.login .obseveOn(MainScheduler.instance) .subscribe( onNext: {}, onError: {} ).disposed(by: disposeBag) } } 34 View Controllerl作成>login

Slide 35

Slide 35 text

class LoginViewModel: UIViewController { // 省略 override func viewDidLoad() { // 省略 vm.login .obseveOn(MainScheduler.instance) .subscribe( onNext: { [weak self] self?.showAlert(for: response) }, onError: { [weak self] error self?.showAlert(for: error) } ).disposed(by: disposeBag) } 35 View Controllerl作成>login

Slide 36

Slide 36 text

class LoginViewModel: UIViewController { @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var emailErrorLabel: UILabel! @IBOutlet weak var passwordTextField: UITextField @IBOutlet weak var passwordErrorLabel: UILabel! @IBOutlet weak var loginButton: UIButton! private let disposeBag = DisposeBag() private let vm = LoginViewModel! override func viewDidLoad() { vm = LoginViewModel ( email: emailTextField.rx.text.orEmpty.asObservable, password: passwordTextField.rx.text.orEmpty.asObservable, loginAPI: LoginAPI.self, disposeBag: disposeBag ) vm.validEmail.bind(to: emailErrorLabel.rx.isHidden).disposed(by: disposeBag) vm.validPassword.bind(to: passwordErrorLabel.rx.isHidden).disposed(by: disposeBag) vm.loginButtonEnable.bind(to: loginButton.rx.isEnable).disposed(by: disposeBag) vm.login .obseveOn(MainScheduler.instance) .subscribe(onNext: { [weak self] self?.showAlert(for: response) }, onError: { [weak self] error self?.showAlert(for: error) }).disposed(by: disposeBag) } } 36 View Controllerl作成>完成

Slide 37

Slide 37 text

● RxSwift概要 ● MVVMパターン概要 ● ViewModel作成 ● ViewController作成 ● まとめ 37 アジェンダ

Slide 38

Slide 38 text

● ViewControllerが肥大化しない ● 処理が明確 ○ テストがしやすい 38 まとめ