Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
RxSwiftでMVVMパターン
Search
tofu_san0000
June 08, 2019
Technology
0
310
RxSwiftでMVVMパターン
Web技術勉強会#02のLT資料です。
RxSwift+MVVMでシンプルなログインフォームを例として解説しました。
tofu_san0000
June 08, 2019
Tweet
Share
More Decks by tofu_san0000
See All by tofu_san0000
Rxのストリームを 感じるために大切なこと
tofu_san0000
0
96
Other Decks in Technology
See All in Technology
ISUCONにPHPで挑み続けてできるようになっ(てき)たこと / phperkaigi2025
blue_goheimochi
0
140
Medmain FACTBOOK
akinaootani
0
110
ルートユーザーの活用と管理を徹底的に深掘る
yuobayashi
6
730
Cloud Native PG 使ってみて気づいたことと最新機能の紹介 - 第52回PostgreSQLアンカンファレンス
seinoyu
2
220
SaaSプロダクト開発におけるバグの早期検出のためのAcceptance testの取り組み
kworkdev
PRO
0
460
RAGの基礎から実践運用まで:AWS BedrockとLangfuseで実現する構築・監視・評価
sonoda_mj
0
440
Proxmox VE超入門 〜 無料で作れるご自宅仮想化プラットフォームブックマークする
devops_vtj
0
160
一人QA時代が終わり、 QAチームが立ち上がった話
ma_cho29
0
290
サーバシステムを無理なくコンテナ移行する際に伝えたい4つのポイント/Container_Happy_Migration_Method
ozawa
1
100
Restarting_SRE_Road_to_SRENext_.pdf
_awache
0
170
ソフトウェア開発現代史: なぜ日本のソフトウェア開発は「滝」なのか?製造業の成功体験とのギャップ #jassttokyo
takabow
2
1.6k
【5分でわかる】セーフィー エンジニア向け会社紹介
safie_recruit
0
20k
Featured
See All Featured
BBQ
matthewcrist
88
9.5k
Designing Experiences People Love
moore
141
23k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
25k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
33
2.1k
Optimising Largest Contentful Paint
csswizardry
35
3.2k
4 Signs Your Business is Dying
shpigford
183
22k
GraphQLの誤解/rethinking-graphql
sonatard
70
10k
The Cult of Friendly URLs
andyhume
78
6.3k
Designing for Performance
lara
606
69k
Visualization
eitanlees
146
16k
Documentation Writing (for coders)
carmenintech
69
4.7k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.2k
Transcript
RxSwiftでMVVMパターン 1 @Web技術勉強会#02 2019/06/08(土) @tofu_san0000
2 @tofu_san0000 豆腐: 木綿派 エディタ: XCode、VSCode、Vim、PHPStorm 言語: Swift、PHP、Vue.js
• RxSwift概要 • MVVMパターン概要 • ViewModel作成 • ViewController作成 • まとめ
3 アジェンダ
• RxSwift概要 • MVVMパターン概要 • ViewModel作成 • ViewController作成 • まとめ
4 アジェンダ
• 各言語で実装されている「Rx」のSwift向けライブラリ • RxKotlin、RxJava、RxJS、RxPHPなどがある • Rx ◦ Reactive Programming ◦
Extensions 5 RxSwift概要
• Observable • Disposable • Operator • Scheduler • etc
... 6 RxSwift概要>メソッド
• 監視することができる対象 • 監視してデータ(イベント)を受け取ることができる • Observableはストリーム(Stream)とも呼ぶ 7 RxSwift概要>メソッド>Observable Observable.of(“a”, “b”,
“c”) .subscribe( // 処理 )
• next ◦ データが正しく流れてきたというイベント • error ◦ エラー(例外)で異常停止した時に流れるイベント • completed
◦ 完了時に発生するイベント 8 RxSwift概要>メソッド>Observable>イベント next next next next error completed
9 RxSwift概要>メソッド>Observable>イベント Observable.of(“a”, “b”, “c”) .subscribe( x in // “a”,
“b”, “c” onNext: { value in // イベント発生時の処理 }, onError: { error in // エラー発生時の処理 }, onCompleted: { // 完了時の処理 } )
• 直訳すると破棄・処分できるもの • Observableをsubscribe(購読)した戻り値 • Disposableに対してdispose()を呼ぶことで 非同期処理のキャンセル • 監視する側としての処理をキャンセル 10
RxSwift概要>メソッド>DisposeBag
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
• Observable、Subjectで作成したストリームを操作できる • ストリームを変換・合成・生成 12 RxSwift概要>メソッド>Operator
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
• 処理をどのスレッドで実行するか指定 14 RxSwift概要>メソッド>Scheduler
• RxSwift概要 • MVVMパターン概要 • ViewModel作成 • ViewController作成 • まとめ
15 アジェンダ
16 MVVMパターン概要 View Controller View Model Event Observable
17 MVVMパターン概要>例
LoginButtonの 有効・無効 18 MVVMパターン概要>例>View Model概要 View Model Input Output Email入力
Password入力 LoginButtonタップ Emailバリデーション Password バリデーション ログイン結果
• RxSwift概要 • MVVMパターン概要 • ViewModel作成 • ViewController作成 • まとめ
19 アジェンダ
20 View Model作成 class LoginViewModel {}
class LoginViewModel { // output lazy var validEmail: Observable<Bool> lazy
var validPassword: Observable<Bool> lazy var loginButtonEnable: Observable<Bool> lazy var login: Observable<LoginResponse> } 21 View Model作成>output定義
class LoginViewModel { // output // 省略 // input init(
email: Observable<String>, password: Observable<String>, loginTap: Observable<Void>, loginAPI: LoginAPI.Type, disposeBag: DisposeBag ) {} } 22 View Model作成>input定義
class LoginViewModel { // output // 省略 // input init(
email: Observable<String>, password: Observable<String>, loginTap: Observable<Void>, loginAPI: LoginAPI.Type, disposeBag: DisposeBag ) { validEmail = email.map { !$0.isEmpty } } } 23 View Model作成>validEmail
class LoginViewModel { // output // 省略 // input init(
email: Observable<String>, password: Observable<String>, loginTap: Observable<Void>, loginAPI: LoginAPI.Type, disposeBag: DisposeBag ) { validEmail = email.map { !$0.isEmpty } validPassword = password.map { !$0.isEmpty } } } 24 View Model作成>validPassword
class LoginViewModel { // output // 省略 // input
init( // 省略 ) { // 省略 loginButtonEnable = Observable.combineLatest { validEmail, validPassword } .map { $0 && $1 } } } 25 View Model作成>loginButtonEnable
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
class LoginViewModel { // output lazy var validEmail: Observable<Bool> lazy
var validPassword: Observable<Bool> lazy var loginButtonEnable: Observable<Bool> lazy var login: Observable<LoginResponse> // input init( email: Observable<String>, password: Observable<String>, loginTap: Observable<Void>, 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作成>完成
• RxSwift概要 • MVVMパターン概要 • ViewModel作成 • ViewController作成 • まとめ
28 アジェンダ
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作成
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作成
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
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
class LoginViewModel: UIViewController { // 省略 override func viewDidLoad() {
vm = LoginViewModel ( // 省略 ) // 省略 vm.loginButtonEnable .bind(to: loginButton.rx.isEnable) .disposed(by: disposeBag) } } 33 View Controllerl作成>loginButtonEnable
class LoginViewModel: UIViewController { // 省略 override func viewDidLoad() {
// 省略 vm.login .obseveOn(MainScheduler.instance) .subscribe( onNext: {}, onError: {} ).disposed(by: disposeBag) } } 34 View Controllerl作成>login
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
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作成>完成
• RxSwift概要 • MVVMパターン概要 • ViewModel作成 • ViewController作成 • まとめ
37 アジェンダ
• ViewControllerが肥大化しない • 処理が明確 ◦ テストがしやすい 38 まとめ