(この記事は CPS Lab Advent Calendar 2016 の25日目の記事です)
Reactive Programmingの流行をみて、とりあえずどんなものかと手探りでチュートリアルをガサゴソしていたのですが、よさげなプレゼンを見つけたので咀嚼してみた。
とりあえずRxSwiftとRxCocoaいれる
- Xcodeで「Single Application」で新規プロジェクトを作成
- ターミナルで当該フォルダまで
cd
bundle init
- Gemfileが生成されるのでGemfileの中に
gem 'cocoapods'
と書いてbundle install
bundle exec pod init
- Podfileが生成されるのでPodfileの中に
pod 'RxSwift' (改行) pod 'RxCocoa'
bundle exec pod install
open (プロジェクト名).xcworkspace
Observable.just()
からはじめるRxSwift
ViewControllerの viewDidLoad()
の中に以下を書いて実行します
let _ = Observable.just(1).subscribe(
onNext: { event in
print(event)
}
)
-
let _ = ...
こう書かないと「返り値があるからそいつ受け止めてくれ〜」って怒られる -
Observable
Rxのちから を使う -
.just
これを宣言すると引数がこの場合はInt
からObservable<Int>
に変わります -
.subscribe
forEachのようなもので、onNext
やonError
などにクロージャを渡すとObservableの要素を順番になめてくれる
複数のObservableを流してみる
Observable.just()
では単一の要素を流すことができました。
次に Observable.from()
を使ってみましょう。
let _ = Observable.from(0...10).subscribe(
onNext: { event in
print(event)
}
)
0...10
はSwiftで範囲を表す記法です
これで実行すると0から10までの数字がコンソールに表示されたことかと思います。
Observable.from()
を使うと、配列の各要素をObservableでラップしてくれ、subscribeで順番になめるということができます。
エラーハンドリングをしてみよう
まず「単一の Error
」をsubscribeしてみましょう。
先にErrorプロトコルを採用したenumを作っておきます。
class ViewController
の宣言の外に書いてください。
enum MyError: Error {
case error
}
次にエラーのObservableを生成してみます。こちらは viewDidLoad()
の中で行います
let _ = Observable.error(MyError.error).subscribe(
onNext: { event in
print(event)
},
onError: { error in
print("エラー! \(error)")
}
)
こう書いて実行すると エラー! error
と表示されます
エラータイプのObservableをsubscribeしたので、 onError
に入りました。
配列の要素の中にエラーを混入させてみる
先ほど試した Observable.from()
を使って、普通の要素の中にエラーを混入させてみます。
enum MyError
はまだ使います。
Observable<Any>.create { observer in
[1, 2, 3, 4].forEach({
observer.on(.next($0))
})
observer.on(.error(MyError.error))
return Disposables.create()
}.subscribe(
onNext: { event in
print(event)
},
onError: { error in
print("エラー! \(error)")
}
)
Observable.create()
というのが出てきましたが、上半分では4つ普通の要素を注入した最後にErrorを注入しています。下半分は変わっていません。 1 2 3 4 エラー! error
という風にコンソールに出てくればOKです。
そしてUITableViewへ
なんか just
とか from
とか subscribe
とか onNext
とかを喋ればなんとかなるということが薄々わかってきたところで、UIの実装をやってみます。
ここまでいろいろ試してきた ViewController.swift
(クラス名 ViewController
)に少し手を加えます。
-
TableViewController.swift
(クラス名TableViewController
)にする - 継承元を
UIViewController => UITableViewController
に - StoryboardからViewControllerを消してTableViewControllerに
- Identity Inspectorのクラスの名前を変更
- Attributes InspectorでIs Initial View Controllerにチェックを入れる
- TableViewのprototype cellを選択してAttributes Inspectorの中からIdentifierに
cell
と設定する
さて、通常の書き方でUITableViewをやると次のようなメソッドをよく書いていることかと思います。
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = indexPath.row
return cell
}
ここでついにRxCocoaの登場です。上のコードは書かずに、次のコードのようにクラスを変更してみましょう。
class TableViewController: UITableViewController {
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = nil
tableView.dataSource = nil
let items = Observable.just((0..<20).map { "\($0)" })
items.bindTo(tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { (row, element, cell) in
cell.textLabel?.text = "\(element)"
}
.addDisposableTo(disposeBag)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
これで実行してみてください。 0
から 19
の要素をラベルにもったセルを生成してやることができました。ここらへんになってくると私もまだ理解できていないのですが、だいたいやっていることとしては
-
disposeBag
というのを使うとメモリリークを防げるらしい - tableViewのdelegateとdataSourceをnilにする(こうしないとエラーで死ぬ)
-
0
から19
という要素の入ったitems
というObservableな配列を生成する。- そして、tableViewにバインドする。
- セルは
cell
と名をつけたprototype cellを使用する - テキストラベルの値に要素の値を利用する
- セルは
- そして、tableViewにバインドする。
という感じでしょうか。
ここまで、Observableの要素を生成するところからObservableな要素群をUIKitのコンポーネントにバインドするところまでをさらってみました。自分も書きながら理解するところがありとてもためになりました。
Reactive Programmingは私的にそそられているので引き続き練習していこうと思っています。しかしあくまでパラダイムの選択は自由なので、「自分が一番使いやすいものを使う」ということが大切です。
参考
- https://realm.io/news/slug-max-alexander-functional-reactive-rxswift/
- https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md
- https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Examples/SimpleTableViewExample/SimpleTableViewExampleViewController.swift
- http://qiita.com/moaible/items/de94c574b25ea4f0ef17#just
- https://newfivefour.com/swift-ios-rxswift.html
- http://qiita.com/erukiti/items/6a82dfd3506e9773d592
- http://tiny-wing.hatenablog.com/entry/2016/01/02/161322