目的
RxSwift の使い方をまとめること
単語の意味も詳しくまとめたい
MVVM で開発を行えるようになること
流れ
1: SPM(Swift Package Manager)を使って RxSwift をインストール
2: ViewController のみで簡単に実装
3: ViewController と ViewModel で実装
4: TextField に入力された文字を Label として表示する
5: 単語集
SPM(Swift Package Manager)を使って RxSwift をインストール
ViewController のみで簡単に実装
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
// MARK: RxSwift
let helloWorldSubject = PublishSubject<String>() // ①: Subject の定義
helloWorldSubject // ②: Subject を購読
// ③: 流れてきたデータを表示
.subscribe(onNext: { message in
print("onNext: \(message)")
})
.disposed(by: disposeBag)
// ④: イベントを流す
helloWorldSubject.onNext("Hello World!_1")
helloWorldSubject.onNext("Hello World!_2")
helloWorldSubject.onNext("Hello World!_3")
helloWorldSubject.onCompleted() // 終了
}
}
ViewController と ViewModel で実装
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private var viewModel: ViewModel!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
viewModel = ViewModel()
// ■ 受け取りに使用する
let helloWorldObservable = viewModel.helloWorldObservable // ①: Subject の定義
// ■ 受け取る, 受け取りを"宣言"する
helloWorldObservable // ②: Subject を購読
// ③: 流れてきたデータを表示
.subscribe(onNext: { [weak self] message in
print("massage: \(message)")
})
.disposed(by: disposeBag)
// ■ 流す
viewModel.updateItem() // ④: イベントを流す
}
}
import RxSwift
import RxCocoa
class ViewModel {
// ■ 受け取りに使用
// Observable: イベントを検知するためのクラス
var helloWorldObservable: Observable<String> {
return helloWorldSubject.asObservable()
}
// ■ 流すために使用
public let helloWorldSubject = PublishSubject<String>()
func updateItem() {
helloWorldSubject.onNext("Hello World!_1")
helloWorldSubject.onNext("Hello World!_2")
helloWorldSubject.onNext("Hello World!_3")
helloWorldSubject.onCompleted() // 終了
}
}
TextField に入力された文字を Label として表示する
bind と Subscribe の2種類で実装している
結果
プログラム
プログラム
import UIKit
import RxSwift
import RxCocoa
import SnapKit // レイアウト
class ViewController: UIViewController {
// MARK: Cells
let sampleTextField: UITextField = {
let textField = UITextField()
textField.text = ""
textField.placeholder = "文字を入力してください"
textField.backgroundColor = UIColor.yellow
return textField
}()
var bindTextLabel: UILabel = {
let label = UILabel()
label.text = ""
label.backgroundColor = .green
label.textColor = .black
return label
}()
var subscribeTextLabel: UILabel = {
let label = UILabel()
label.text = ""
label.backgroundColor = .red
label.textColor = .black
return label
}()
private let disposeBag = DisposeBag()
private var viewModel: ViewModel!
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
setupBinding()
viewModel = ViewModel()
// ■ 受け取りに使用する
let helloWorldObservable = viewModel.helloWorldObservable // ①: Subject の定義
// ■ 受け取る, 受け取りを"宣言"する
helloWorldObservable // ②: Subject を購読
// ③: 流れてきたデータを表示
.subscribe(onNext: { [weak self] message in
print("massage: \(message)")
})
.disposed(by: disposeBag)
// ■ 流す
viewModel.updateItem() // ④: イベントを流す
}
// SnapKit を使って画面を描画
private func setupLayout() {
view.backgroundColor = .blue
view.addSubview(sampleTextField)
view.addSubview(bindTextLabel)
view.addSubview(subscribeTextLabel)
sampleTextField.snp.makeConstraints { make in
make.width.equalTo(300)
make.height.equalTo(100)
make.top.equalTo(view.snp.top).offset(100)
make.centerX.equalToSuperview()
}
bindTextLabel.snp.makeConstraints { make in
make.width.equalTo(300)
make.height.equalTo(100)
make.top.equalTo(sampleTextField.snp.bottom).offset(20)
make.centerX.equalToSuperview()
}
subscribeTextLabel.snp.makeConstraints { make in
make.width.equalTo(300)
make.height.equalTo(100)
make.top.equalTo(bindTextLabel.snp.bottom).offset(20)
make.centerX.equalToSuperview()
}
}
private func setupBinding() {
// bind を使用
sampleTextField.rx.text
// ↓変換前 ↓変換後
.map { text -> String? in
guard let text = text else { return nil }
return "あと\(10 - text.count)文字です"
}
.bind(to: bindTextLabel.rx.text)
.disposed(by: disposeBag)
// subscribe を使用
sampleTextField.rx.text
.subscribe(onNext: { [weak self] text in
self?.subscribeTextLabel.text = text
})
.disposed(by: disposeBag)
}
}
import RxSwift
import RxCocoa
class ViewModel {
// 変更なし
}
👇重要な部分を抜粋
// bind を使用, 黄緑
sampleTextField.rx.text
.bind(to: bindTextLabel.rx.text)
.disposed(by: disposeBag)
// subscribe を使用, 赤
sampleTextField.rx.text
.subscribe(onNext: { [weak self] text in
self?.subscribeTextLabel.text = text
})
.disposed(by: disposeBag)
Operator を使ってデータの加工
sampleTextField.rx.text
// ↓変換前 ↓変換後
.map { text -> String? in
guard let text = text else { return nil }
return "あと\(10 - text.count)文字"
}
.bind(to: bindTextLabel.rx.text)
.disposed(by: disposeBag)
単語集
RxCocoa
: UIKitやAppKit向けのextensionがswiftで集められたもの[1]
DisposeBag
: イイ感じに購読を破棄して、メモリーリークを回避するための仕組み[2]
Observable
: 観測可能なものを表現しイベントを検知するためのクラス, イベント発生元, イベントを検知するためのクラス[2]
Observaer
: イベント処理[2]
PublishSubject
: イベント検知に加えて発生(イベント内容/エラー/完了)を行うクラス[2]
Subject
: 「通信処理やDB処理等」エラーが発生したときにその内容によって処理を分岐させたい[2]
Relay
: UIに値をBindする[2]
subscribe
: Observableから通知を受け取ったらどのような振る舞いをするかを定義する[3]
onNext
: 送られてきたイベントを受け取る
bind
: 値を代入すること, 単方向データバインディング
Operator
: Obserbableに対してイベントの値を加工して、新しく** を作成する仕組み[2]
Operator の種類とメソッドと役割[2],[4],[5]
種類 | メソッド名 | 役割 |
---|---|---|
変換 | map | データ(nilを含む)を変換して通す |
flatMap | データを1次元にして返す | |
reduce | 全ての要素の累積を求める | |
scan | reduce の途中結果をイベント発行できるもの | |
debounce | 指定時間イベントが発生しなかった場合、最後に流されたイベントを流す | |
絞り込み | filter | 指定した条件を満たす要素だけを含む配列を返す |
take | 指定時間の間だけイベントを通知して onCompleted する | |
skip | 指定時間の間はイベントを無視する | |
distinct | 重複イベントを除外する | |
組み合わせ | zip | 複数の Observable を組み合わせる(異なる型同士 可) |
merge | 複数の Observable を組み合わせる(異なる型同士 不可) | |
combineLatest | 複数の Observable の最新値を組み合わせる | |
sample | 引数に Observable のイベントを渡し、イベント発生時に元の Observable の最新イベントを通知 | |
concat | 複数の Observable のイベントを順番に組み合わせる(異なる方同士 不可) |
Driver
: UIに特化したObservable, 「エラーを流さない・メインスレッドの保証」という利点がある。UIViewController でのバインディングに推奨されている (swiftにはUIの描画や更新はメインスレッドで行わなければならないというルール), Driver型に変換する ようなイメージで使用する。
参考資料
[1]. RxSwiftでUIKitを扱うにはRxCocoaが必要
[2]. 比較して学ぶRxSwift入門 (技術の泉シリーズ(NextPublishing)) Kindle版 ← ⭐️ 筆者おすすめ
[3]. 入門!RxSwift ← ⭐️ 筆者おすすめ
[4]. 【Swift】シーケンスの高階関数まとめ
[5]. イメージで理解するSwiftの高階関数(filter, map, reduce, compactMap, flatMap) - Qiita
[6]. RxCocoa 4 の Signal と Relay のまとめ - mercari engineering
[7]. RxCocoaが提供するDriverって何? - Qiita