Swift
RxSwift
swift4

RxSwiftのシンプルな説明

Swiftで簡単なアプリを写経するのは簡単なのであるが、やはり実用的なアプリを作成するには、レスポンシブでないと役な立たない(お客さん受けしない)よねということで、RxSwiftを学んでみた。

正直、3日間くらい、泣きながら、挫けそうになりながら、禿げそうになりながら食らいついていた。

はっきりいって理解するのがキツイ。

開発環境

OS : MacOS High Sierra Version 10.13.6
Xcode : Version 9.4.1

サンプリアプリ

ボタンを押すとカウントアップするだけのアプリ。

スクリーンショット 2018-08-09 14.11.12.png

ソースコード解説

ViewController.swiftのコードを説明する。

まずは、必要なライブラリをimportする。ここでは、RxSwiftを利用する。

ViewController.swift
import UIKit
import RxSwift

先に、ラベルとボタンを配置する。
StoryBoardでも良いのであるが、敢えてコードで記述。

ViewController.swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Label
        let countLbl = UILabel()
        countLbl.text = "0"
        countLbl.textAlignment = .center
        countLbl.textColor = UIColor.blue
        countLbl.frame = CGRect(x: (self.view.frame.width-110)/2, y: 150, width: 110, height: 21)

        // Button
        let countUpBtn = UIButton()
        countUpBtn.frame = CGRect(x: (self.view.frame.width-100)/2, y: 200, width: 100, height: 30)
        countUpBtn.layer.borderWidth = 1.0
        countUpBtn.layer.borderColor = UIColor.blue.cgColor
        countUpBtn.layer.cornerRadius = 5.0
        countUpBtn.setTitleColor(UIColor.blue, for: .normal)
        countUpBtn.setTitle("カウントUP", for: .normal)
        countUpBtn.addTarget(self, action: #selector(ViewController.countUp(_:)), for: UIControlEvents.touchUpInside)

        self.view.addSubview(countLbl)
        self.view.addSubview(countUpBtn)
}

次に、RxSwiftの肝であるクラスを作成する。

ViewController.swift
class RxSwiftSample {

}

RxSwiftSampleクラスには、private宣言されたsubjectPublishSubject<Int>()でインスタンス化して保持しておく。それと、そのsubjectをObservableとして返すeventを定義する。
そして、実際の処理を外部から呼べる myFunc()メソッドを定義する。その中で、onNextとかいうイベントを発行する。

これが基本的な雛形という感じかな?

ViewController.swift
class RxSwiftSample {
    private let subject = PublishSubject<Int>()

    var event : Observable<Int>{
        return subject
    }

    func myFunc(){
        // 処理
        print("myFuncが呼ばれました。")
        // イベント発行
        subject.onNext(self.data)
    }
}

次に、このRxSwiftSampleクラスの使い方なのであるが、ViewControllerから利用できるように、RxSwiftSampleをモデルとしてmodelで宣言しておく。また、後で利用するdisposeBagも宣言しておく。

ViewController.swift
private let model = RxSwiftSample()
private let disposeBag = DisposeBag()

上記を、ViewControllerから利用する。
先ずは、順を追ってわかりやすく、ボタンに対して、countUp(_ sender: UIButton)が呼ばれるように記述する。これは特に問題はないはず。
重要なのは、ここからmodelmyFunc()を呼び出すことである。

ViewController.swift
    override func viewDidLoad() {
     //ボタンへイベント登録    
        countUpBtn.addTarget(self, action: #selector(ViewController.countUp(_:)), for: UIControlEvents.touchUpInside)
    }

    @objc func countUp(_ sender: UIButton){
        print("カウントアップ")    
        //Modelの関数呼び出し
        model.myFunc()
    }

ここから、理解が進むと思うが、model.myFunc()で処理を行うと、事前に登録されているsubscribeで通知される。そして、 subject.onNext(self.data)で渡されたパラメータも、value で受け取れる。

ViewController.swift
        //Reactive処理
        model.event.subscribe(
                onNext: {value in
                    print("ここで通知")                
                    countLbl.text = String(value)
                })
                .disposed(by: disposeBag)

これで、ようやく頭の中で処理シーケンスがつながった。
それでも理解できない場合は、私と同じように1日悩んで、print()仕掛けて、処理を追ってみると良いと思う。

やはり、このようなフレームワークやデザインパターンの理解は正直キツイ。それでも、時間を掛けて手を動かすのが、ベストプラクティスだと思う。

その助けになればと祈る。情報がちょっと時代遅れ感はあるのだが・・・。
始めたのが遅いので仕方ない。w

最後に

ちなみに、RxSwiftの何が嬉しいのかということだが、例えば、カウントが10以上になったら、ラベルとかボタンの色を赤に変えるとかが、一箇所に簡単に書けます。

ViewController.swift
        //Reactive処理
        model.event.subscribe(
                onNext: {value in
                    countLbl.text = String(value)
                    //
                    if(value > 10) {
                        countLbl.backgroundColor = UIColor.red
                    }
                })
                .disposed(by: disposeBag)

なかなかメリットが多そうです。

ここに記したコードは、自分がRxSwiftの構造を理解するために作成したコードです。間違い等がございましたらご指摘願います。

下記、全ソースコードです。

ViewController.swift
import UIKit
import RxSwift


private let model = RxSwiftSample()
private let disposeBag = DisposeBag()

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Label
        let countLbl = UILabel()
        countLbl.text = "0"
        countLbl.textAlignment = .center
        countLbl.textColor = UIColor.blue
        countLbl.frame = CGRect(x: (self.view.frame.width-110)/2, y: 150, width: 110, height: 21)

        // Button
        let countUpBtn = UIButton()
        countUpBtn.frame = CGRect(x: (self.view.frame.width-100)/2, y: 200, width: 100, height: 30)
        countUpBtn.layer.borderWidth = 1.0
        countUpBtn.layer.borderColor = UIColor.blue.cgColor
        countUpBtn.layer.cornerRadius = 5.0
        countUpBtn.setTitleColor(UIColor.blue, for: .normal)
        countUpBtn.setTitle("カウントUP", for: .normal)
        countUpBtn.addTarget(self, action: #selector(ViewController.countUp(_:)), for: UIControlEvents.touchUpInside)

        self.view.addSubview(countLbl)
        self.view.addSubview(countUpBtn)

        //Reactive処理
        model.event.subscribe(
                onNext: {value in
                    countLbl.text = String(value)
                })
                .disposed(by: disposeBag)
    }

    @objc func countUp(_ sender: UIButton){
        //Modelの関数呼び出し
        model.myFunc()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

class RxSwiftSample {
    private let subject = PublishSubject<Int>()
    private var data = 0

    var event : Observable<Int>{
        return subject
    }

    func myFunc(){
        // 処理
        data = data + 1

        // イベント発行
        subject.onNext(self.data)
    }
}

Lovly Swift!!!