2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Swift × RxSwift】 RxSwift を使ってコード実装してみた!

Last updated at Posted at 2022-03-08

目的

RxSwift の使い方をまとめること
単語の意味も詳しくまとめたい
MVVM で開発を行えるようになること

流れ

1: SPM(Swift Package Manager)を使って RxSwift をインストール
2: ViewController のみで簡単に実装
3: ViewController と ViewModel で実装
4: TextField に入力された文字を Label として表示する
5: 単語集

SPM(Swift Package Manager)を使って RxSwift をインストール

こちらを参考

ViewController のみで簡単に実装

ViewController.swift
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 で実装

ViewController.swift
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()   // ④: イベントを流す
    }

}
ViewModel.swift
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種類で実装している

結果

プログラム

プログラム
ViewController.swift
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)
    }

}
ViewModel.swift
import RxSwift
import RxCocoa

class ViewModel {
    // 変更なし
}

👇重要な部分を抜粋

ViewController.swift
// 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 を使ってデータの加工

ViewController.swift
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

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?