
Reactiveは哲学だ! with Swift
おばんです、仙台の田中です。
この度「Reactive Programming in Swift」と題しまして、Sendai.swift 第一回を開催いたしました。
参加者の皆さんのおかげで議論できるとても有意義な会となりました。Reactiveとはなんなのかを実際にライブラリを使ってみて動きを追いつつ、定義ってこういうことらしいという話や、ライブラリの挙動の考察、便利なところについて話したり楽しかったです、ありがとうございます!
英語間違えたのはここの読者さまと僕だけの秘密にしてください。
英文学科出身 #とは
Sendai.swiftで取り扱った内容に沿いながら、
このエントリではライブラリでの実装例が多い昨今、逆に「Reactiveってなに?」ってところから解説していこうと思います。
「違う!Reactiveというのはだな...!」というReactive警察の方はあとでこっそり取り締まr...、ご指摘お待ちしております!(認識が甘い部分があるので助かります!)
このエントリの内容
- 昨今話題のReactive Programmingとは?
- iOS標準におけるReactiveの実装例
- SwiftのReactiveライブラリ解説
- Reactiveのうまみ、つらみ
- まとめ
- 参考にしたサイト
1. 昨今話題のReactive Programmingとは?
こちらが勉強会資料になります。
Reactive Manifesto
ズバリ「Reactiveってなに?」ということが文字でまとめてあるのがReactive Manifestoになります。
元は英語になっていますが調べると和訳されたものがいくつか出てきますのでそちらを参考になさるのも良いと思います。
訳がそれぞれ違かったり、バージョンによる違いなどもありますので読み比べて概要を掴むと良いでしょう。
Reactiveなシステムには以下の4つの重要な要素があります。
- Responsive: 即応性
- Resilient: 耐障害性
- Elastic: 弾力性
- Message Driven: メッセージ駆動
Responsive: 即応性
即応性というのはエンドユーザーに素早いレスポンスが与えられるということです。
ユーザーのアクションに対して即座に何らかのレスポンスが返る、これは正常動作であれ、どこかでコケて表示されるAlertであれ、素早くです。
例えばUIに関して言うと、Twitterの「いいね」(Fav)ボタン。
ボタンは押したあとすぐにハートマークが点灯し、裏ではツイートに対してFavする通信を走らせています。
非同期の通信に失敗したあとは点灯したいいねボタンは消灯します。
この例のようにユーザーのアクションが即座に反映されることはユーザーの誤解やストレスの高まりを無くす良いUXといえるでしょう。
Resilient: 耐障害性
二つ目は耐障害性。
なんらかの障害に直面してもなお、即応性を失わないという要素です。
できるだけ落ちないようにする意味ではサーバーの冗長化などもここに含まれます。
例えば、通信が行えない(あるいはサーバーが落ちている)状態でもクライアントは正常に動作し続けるようであるべきという話です。
サーバー側で何らかのトラブルが発生して通信が正しく行われない場合、クライアントはその間のユーザーのアクションを保持しておいて、正常に動作するようになったときに同期処理をかけるようにする仕様などが挙げられます。
これもユーザーに良いUXを届けるために重要なことです。
Elastic: 弾力性
三つ目は弾力性。
ワークロード(負荷、仕事量)が変化しても即応性を保つこと。
負荷は適当に分散処理されるかどうかということであったり、サーバーがオートスケールするかどうかなどです。
Message Driven: メッセージ駆動
四つ目はメッセージ駆動。
これはObserverパターンのことかと認識しました。iOSだとNSNotificationCenterを使った通知など。
送信者と受信者がそれぞれで独立したライフサイクルを持っていて、各個セットが非同期にそれぞれでやりとりを行う。
上に共通することとして「即応性」を意識する点が挙げられます。
クライアントとサーバーで具体的に即応性を実現するために意識する部分は変わってきますが、一貫して即応性を保ち続けることがReactiveの哲学である、と僕はとらえました。
2. iOS標準におけるReactiveの実装例
さて、ではReactiveで大切なことは即応性を保つことでした。
モバイルのUIを題材に見ていきます。
なんらかのイベントや変更を検知してUI要素の書き換えを行うことはよくあるのではないでしょうか。
例えばUITextFieldの変更を検知してUILabelにその値を反映させるなどです。
パッと思いつく方法は
- addTargetする
- delegateを使う
- NSNotificationCenterで通知する
の三つでしょうか。
それぞれの検知部分のコードを見ていきましょう。
addTargetする
import UIKit | |
class ViewController: UIViewController, UITextFieldDelegate { | |
@IBOutlet weak var textField: UITextField! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
textField.addTarget(self, action: "hoge:", forControlEvents: UIControlEvents.EditingChanged) | |
} | |
// !!! 処理が遠い&ジャンプできない !!! | |
func hoge(textField: UITextField) { | |
print(textField.text) | |
} | |
} |
delegateを使う
import UIKit | |
// !!! プロトコルの採用とDelegateのセット忘れでつまづく(※ここではStoryboard上でVCと紐付けています) !!! | |
// textField.delegate = selfのことです。 | |
class ViewController: UIViewController, UITextFieldDelegate { | |
@IBOutlet weak var textField: UITextField! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
} | |
// !!! 複数のDelegateメソッドがある際に処理の順序が追いづらい !!! | |
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { | |
print(string) | |
return true | |
} | |
} |
NSNotificationCenterで通知する
import UIKit | |
class ViewController: UIViewControlle { | |
@IBOutlet weak var textField: UITextField! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// !!! なんか長い !!! | |
NSNotificationCenter.defaultCenter().addObserver(self, selector: "textDidChange:", name: UITextFieldTextDidChangeNotification, object: textField) | |
} | |
// !!! 処理が遠い&ジャンプできない !!! | |
func textDidChange(notification: NSNotification) { | |
print((notification.object as? UITextField)?.text) | |
} | |
// !!! deinit時にremoveし忘れる !!! | |
deinit { | |
NSNotificationCenter.defaultCenter().removeObserver(self) | |
} | |
} |
いかがでしょうか。
iOS開発あるあるなちょっと辛いところをコメントで挙げてみました。
(加えてshouldChangeCharactersInRangeの扱いにはちょっとしたクセもあったような気もします)
それぞれ共通するデメリットとして以下があるかと思います、
- Observerのadd部分と実際の処理が離れてしまう
- Observerのadd部分から実際の処理にジャンプできない
- (NSNotificationCenterは特に処理が遠くにありがち)
あとNSNotificationCenterのremoveし忘れとかつらみを感じます。
上記でも即応性を保っているので、定義としてReactiveなプログラムであると言えます。
しかし読みづらい
3. SwiftのReactiveライブラリ解説
それを解決するために、ライブラリでデータバインディングの仕組みを使うとすっきりします。
Reactiveライブラリはいくつかありますが、ここではRxSwiftを取り上げます。
インストール方法や詳しい事はこちらのRxSwiftのGitHubリポジトリを参照してください。
サンプルはRxSwiftのサンプルとSwiftBondのサンプルをGithubにあげておきました。
自分の勉強も兼ねているので今後アップデートしていくかもしれません。
先ほど取り上げたUITextFieldの変更を検知してUILabelに反映させることを題材に見ていきます。
RxSwiftで実装すると
import UIKit | |
import RxSwift | |
import RxBlocking | |
import RxCocoa | |
class ViewController: UIViewController { | |
@IBOutlet weak var textField: UITextField! | |
@IBOutlet weak var label: UILabel! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// !!! これだけ !!! | |
textField.rx_text.subscribeNext { [unowned self] text in | |
self.label.text = text | |
} | |
} | |
} |
これだけ!
iOS標準のやり方で現れていたデメリット、Observerのadd部分と実際の処理が離れてしまう問題が解決しました。
UITextFieldの拡張で追加されているrx_textというプロパティがObserverとしての役割を果たしています。
アプローチとしては関数型プログラミング的で、処理を順々に行っていくPromiseな感じになってます。
検知したら次の処理に変更のあった内容を渡して、その処理もまた内容を加工したりフィルタリングして次に渡す。
これがFunctional Reactive Programmingが話題になっているという文脈になります。
例を加えましょう。
「textFieldの文字列が三文字以上ならlabelに値を反映する」条件を加えてみます。
import UIKit | |
import RxSwift | |
import RxBlocking | |
import RxCocoa | |
class ViewController: UIViewController { | |
@IBOutlet weak var textField: UITextField! | |
@IBOutlet weak var label: UILabel! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
textField.rx_text.filter { text -> Bool in | |
return text.characters.count >= 3 | |
}.subscribeNext { [unowned self] text in | |
self.label.text = text | |
} | |
} | |
} |
Rectiveの考え方については触れましたが、その先としてライブラリを扱ううえでの考え方やアプローチに関しても学ぶ必要があります。
Stream, Observer, Subscribe, Subject, Cold Observer, Hot Observer, filter, map, flatmap, reduce, etc...
ここに関してはまた別エントリで紹介できればと思います。
4. Reactiveのうまみ、つらみ
うまみ
- コード量削減
- イベントの記述や条件が一まとまりなので読みやすい
- Functionalなとこも書けるとドヤれる気がする
つらみ
- 学習コストが高い
純粋にUI要素同士のデータバインディングくらいであれば重くないですが、関数型的なアプローチの部分に慣れるのに学習コストが高いかもしれません。
しかし実際に例示したようにコード量が削減されたり読みやすさが段違いなので導入価値は大きくあるように思います。
5. まとめ
Reactive自体は哲学であって、その手段・アプローチに関してはいくつかありますが関数型プログラミングのアプローチが有効。
そこを補ってくれるのがRx系やSwiftBondのReactiveライブラリである。
「Reactiveってなに?」という定義から入るよりまずライブラリを使ってみたほうが良いかもしれません。
が、ライブラリの解説記事はいっぱいあるので「Reactiveってこうだよ!」というところを個人的にまとめてみました。
Reactiveは言語を問わず使える考え方のようだったのでとても勉強しがいがありました。
これからライブラリを実際に使ってみて、その所感などまたまとめられればと思います。
6. 参考にしたサイト
【Reactiveとは?】
「RxJS」初心者入門 – JavaScriptの非同期処理の常識を変えるライブラリ
【翻訳】あなたが求めていたリアクティブプログラミング入門
The Reactive Manifesto
リアクティブ宣言
【Functionalとは?】
Functional Programming in Swift
Swiftで学ぶ関数型 〜 immutable 〜
Swiftでの関数型プログラミングについて考えていること
Swift,Objective-Cプログラミング ~ iOS ~
【Reactiveライブラリの使い方】
ニコニコ漫画アプリの中身、全部見せます! 〜iOSアプリ開発事例のご紹介〜
Swift bondでつなげてプログラミング
RxSwift Observable生成関数まとめ
RxSwiftで双方向データバインディング
RxSwiftをインストールして簡単に使ってみる
RxSwiftを使ってGitHubのおすすめユーザーを表示するアプリをつくってみた