Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
RxSwiftでMVVMパターン
Search
tofu_san0000
June 08, 2019
Technology
0
360
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
100
Other Decks in Technology
See All in Technology
ローカルVLM OCRモデル + Gemini 3.0 Proで日本語性能を試す
gotalab555
1
250
その意思決定、まだ続けるんですか? ~痛みを超えて未来を作る、AI時代の撤退とピボットの技術~
applism118
45
25k
Symfony AI in Action
el_stoffel
2
280
pmconf 2025 大阪「生成AI時代に未来を切り開くためのプロダクト戦略:圧倒的生産性を実現するためのプロダクトサイクロン」 / The Product Cyclone for Outstanding Productivity
yamamuteki
3
3.2k
履歴テーブル、今回はこう作りました 〜 Delegated Types編 〜 / How We Built Our History Table This Time — With Delegated Types
moznion
13
8.7k
Introduction to Sansan for Engineers / エンジニア向け会社紹介
sansan33
PRO
5
46k
MySQL AIとMySQL Studioを使ってみよう
ikomachi226
0
120
DDD x Microservice Architecture : Findy Architecture Conf 2025
syobochim
13
7.2k
MAP-7thplaceSolution
yukichi0403
2
220
命名から始めるSpec Driven
kuruwic
3
730
都市スケールAR制作で気をつけること
segur
0
210
20251127 BigQueryリモート関数で作る、お手軽AIバッチ実行環境
daimatz
0
390
Featured
See All Featured
Faster Mobile Websites
deanohume
310
31k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
132
19k
KATA
mclloyd
PRO
32
15k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
Product Roadmaps are Hard
iamctodd
PRO
55
12k
Why Our Code Smells
bkeepers
PRO
340
57k
Optimising Largest Contentful Paint
csswizardry
37
3.5k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Producing Creativity
orderedlist
PRO
348
40k
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
360
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
359
30k
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 まとめ