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
330
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
97
Other Decks in Technology
See All in Technology
論文紹介:LLMDet (CVPR2025 Highlight)
tattaka
0
110
AI導入の理想と現実~コストと浸透〜
oprstchn
0
120
データプラットフォーム技術におけるメダリオンアーキテクチャという考え方/DataPlatformWithMedallionArchitecture
smdmts
5
660
KubeCon + CloudNativeCon Japan 2025 Recap
ren510dev
1
250
整頓のジレンマとの戦い〜Tidy First?で振り返る事業とキャリアの歩み〜/Fighting the tidiness dilemma〜Business and Career Milestones Reflected on in Tidy First?〜
bitkey
0
100
OpenHands🤲にContributeしてみた
kotauchisunsun
1
480
Oracle Cloud Infrastructure:2025年6月度サービス・アップデート
oracle4engineer
PRO
2
290
PHPでWebブラウザのレンダリングエンジンを実装する
dip_tech
PRO
0
210
A2Aのクライアントを自作する
rynsuke
1
220
生成AI時代 文字コードを学ぶ意義を見出せるか?
hrsued
1
660
rubygem開発で鍛える設計力
joker1007
2
220
"サービスチーム" での技術選定 / Making Technology Decisions for the Service Team
kaminashi
1
190
Featured
See All Featured
A Tale of Four Properties
chriscoyier
160
23k
Stop Working from a Prison Cell
hatefulcrawdad
270
20k
How to Think Like a Performance Engineer
csswizardry
24
1.7k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.8k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
31
1.2k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
667
120k
What's in a price? How to price your products and services
michaelherold
246
12k
Statistics for Hackers
jakevdp
799
220k
Fireside Chat
paigeccino
37
3.5k
Become a Pro
speakerdeck
PRO
28
5.4k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
2.9k
The Art of Programming - Codeland 2020
erikaheidi
54
13k
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 まとめ