初めまして。Qiita初投稿、新卒iOSエンジニアです。
自分の性格上、こういう風に自分の記事を投稿するなんてこと絶対にないと思っていたのですが、Qiitaの記事にはいつも本当にお世話になっており、なんとかこの感謝の気持ちを還元していきたいと思ったので、この度お礼の意味を込めて記事を投稿することにいたしました。
本題です
最近RxSwiftに触る機会があったのですが、もう本当にちんぷんかんぷんでかなり苦労しました。(まだしてる。)
自分はプログラミング歴自体まだ2年いかないくらいのペーペーなので、RxSwiftの説明を聞いたところで本当に一から十まで意味不明。
詳しく説明してくれるのはいいんやけど専門用語とか使われたらもう謎やねんけど。ええからとりあえず一個簡単な具体例見せてくれん?!
てことで自分で考えてみました。
実際に作りたいのは以下のようなアプリ。
ボタンをタップすると今までタップした回数が表示されるというシンプルなものです。簡単ですね。
これをRxSwiftを使って作りたいと思います。本当はもっと複雑なものの方がRxSwiftの真価を発揮できるのですが、超入門編ということであえてこの要件で進めていきます。
Delegateパターン・CallBack・KVOパターンを使って実装したものも作り、それぞれの違いをわかりやすく比較できるようにしたいと思います。
「RxSwiftってなあに?新車??」てレベルの方向けの記事なので、長い説明は省きたいと思います。わかりやすそうだと思った実装とRxSwiftの実装を見比べて見て「ああ〜、車の話じゃないんだぁ〜」てなってくれたら幸いです。
ので、全文読む必要は全くないです。
なお、いずれの実装でも、
この形を崩さずに実装していくことを前提とします。
まず、Delegateパターンを使った実装
import UIKit
class DelegateViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var label: UILabel!
private let model = DelegateModel()
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(tappedBtn), for: .touchUpInside)
// モデルのデリゲートを自身に設定
model.delegate = self
}
@objc func tappedBtn(sender: UIButton) {
model.updateData ()
}
}
// デリゲートメソッドにモデルの値が変更された時に実行したい処理を記述
extension DelegateViewController: HogeDelegate {
func displayCount(data: Int) {
label.text = String(data) + "回押したよ(delegate版)"
}
}
// デリゲートプロトコル
protocol HogeDelegate: class {
func displayCount(data: Int)
}
class DelegateModel {
private var data: Int = 0
// デリゲート
weak var delegate: HogeDelegate?
func updateData() {
self.data += 1
// dataの値を更新後、デリゲートメソッドを実行
self.delegate?.displayCount(data: data)
}
}
このパターンはiOSアプリエンジニアの方なら最も親しみやすいかと思います。
デリゲートプロトコルを用意することで、モデル側で値が変更された時に実行したい処理を、ビュー側に委譲します。
そうすることで ビュー <=> モデル 間 の連携を実現します。
CallBackを使った実装
import UIKit
class CallBackViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var label: UILabel!
private let model = CallBackModel()
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(tappedBtn), for: .touchUpInside)
}
@objc func tappedBtn(sender: UIButton) {
// モデルの値を変更した時に、実行したい処理もクロージャで渡す
model.updateData (callback: ) { data in
label.text = String(data) + "回押したよ(callback版)"
}
}
}
class CallBackModel {
private var data: Int = 0
func updateData(callback: (Int) -> ()) {
self.data += 1
// dataの値を更新後、ビュー側から渡されてきたクロージャを実行
callback(self.data)
}
}
このくらい簡単な要件なら、callbackを使った実装が一番短く簡単に済みそうです。
callbackを使った実装のメリットは、処理の記述を実行したいタイミングのそばにかけるのところです。(ビュー側で'model.updateData'メソッドを呼ぶ時に、実行したい処理をクロージャで記述しちゃえるという意味です。)
ただもう少し複雑な要件になった時にこれがデメリットにもなるですが、先に述べたとおり長い説明は省きたいと思います。
KVOパターンを使った実装
KVOとは Key-Value-Observing の略で、オブジェクトのプロパティの変更を監視する仕組みのことです。
おそらく一番見慣れない感じの実装だろうと思いますが、RxSwiftの考え方に一番近いものかなと思ったので紹介しておきます。
import UIKit
class KVOViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var label: UILabel!
private let model = KVOModel()
// 監視オブジェクト。こいつがいることでオブジェクトの監視が可能となる。
private var modelObservation: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(tappedBtn), for: .touchUpInside)
// 監視オブジェクト。Modelの値が変更された時に実行したい処理を設定しておく。
modelObservation = model.observe(\.data, options: .new) {model, change in
if let newValue = change.newValue {
self.label.text = String(newValue) + "回押したよ(KVO版)"
}
}
}
@objc func tappedBtn(sender: UIButton) {
model.updateData ()
}
}
class KVOModel:NSObject {
// Cocoaの機能であるobjc_msgSendでアクセスされるようにするため、「@objc dynamic」の記述が必要。(笑)
@objc dynamic var data: Int = 0
func updateData() {
self.data += 1
}
}
満を持して、RxSwiftを使った実装
RxSwiftとは。みたいな説明はうざいので省きます。
上のKVOパターンみたいに、オブジェクトのプロパティの変更を監視する仕組みを実現するのに超便利なライブラリだと思っておいてください。
import UIKit
// RxSwiftライブラリをインポート
import RxSwift
class RxSwiftViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var label: UILabel!
private let model = RxSwiftModel()
// subscribe(購読・観察・監視)を解除するために必要。こいつのおかげでこのVCオブジェクトが解放されるタイミングで自動的に購読の解除を行ってくれる。(笑)
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(tappedBtn), for: .touchUpInside)
// モデルのsubscribeを開始。
model.event
.subscribe (
// モデルの値が変更された時に実行したい処理を記述
onNext: { value in
self.label.text = String(value) + "回押したよ(RxSwift版)"
})
// subscribeを解除するために必要。(笑)
.disposed(by: disposeBag)
}
@objc func tappedBtn(sender: UIButton) {
model.updateData ()
}
}
class RxSwiftModel {
private var data = 0
// 監視されるのはこのオブジェクト。 ビュー側に公開するためのeventプロパティ(getOnly)を作る理由は、外からsubjectプロパティを直接参照してOnNextメソッドを自由に呼ばれてしまったりするのを防ぐため。
private let subject = PublishSubject<Int>()
var event: Observable<Int> {
return subject
}
func updateData() {
self.data += 1
// dataの値を更新後、イベント発行
subject.onNext(data)
}
}
要件が簡単すぎたせいで、一番記述量が多い実装になってしまいました笑
結
比較しやすいように、できるだけ似せた記述を心がけました。説明を省いてしまったので、コピペして実際に動かしてみていただいた方がわかりやすいと思います。(ボタンとラベルは自分で用意してね。)
もっと複雑な要件のほうがRxSwiftの真価を発揮できるとはじめにも言いましたが、それについてはもしまた別の機会があれば書けたらいいなと思います。(今回の記事の手応え次第です。。)
RxSwiftについてのより詳しい説明としては
で書かれている内容がとてもわかりやすかったのでオススメです。
最後まで読んでくださってありがとうございました。
この記事の内容が少しでも読んでくださった方の力になれば幸いです。