ずっと気になっていて使ったことがなかったので、今更ながらRxSwiftを触ってみました。
RxSwiftとは
C#用のライブラリReactive Extensions
が様々な言語に派生しているうちのSwift版となります。
非同期イベントを受け取る為の枠組みが用意されており、様々な処理を簡易に書けるのが強みの模様。
学習コストが高いらしいですが、他の言語でも同じように書けるようなのでその辺りは良いのかもしれません。
RxSwiftの導入
私はCarthageで導入しました。
Cartfileに以下を記載します。
github "ReactiveX/RxSwift"
下記のコマンドを実行します。
$ carthage update --platform iOS --no-use-binaries
*** Fetching RxSwift
*** Checking out RxSwift at "4.4.0"
*** xcodebuild output can be found in /var/folders/qg/qs42kxfj49z3bly2_f9v0cdh0000gn/T/carthage-xcodebuild.2pAGBX.log
*** Building scheme "RxAtomic" in Rx.xcworkspace
*** Building scheme "RxBlocking" in Rx.xcworkspace
*** Building scheme "RxCocoa" in Rx.xcworkspace
*** Building scheme "RxSwift" in Rx.xcworkspace
*** Building scheme "RxTest" in Rx.xcworkspace
ビルドされた.framework
をxcodeprojへ追加します。
[TARGETS]-[General]-[Linked Frameworks and Library]の+ボタンから、[Add Other...]で追加した.framework
を選択します。
今回はRxSwift.framework
とRxCocoa.framework
を追加しました。
[TARGETS]-[Build Phases]の+ボタンから、[New Run Script Phase]で項目を追加します。
追加された項目のshellに以下を記載
/usr/local/bin/carthage copy-frameworks
InputFilesに以下を追加
$(SRCROOT)/Carthage/Build/iOS/RxSwift.framework
$(SRCROOT)/Carthage/Build/iOS/RxCocoa.framework
以上でRxSwiftを利用する準備は完了
使ってみる
まずはどんな感じなのか、簡単に使ってみます。
とりあえず、UITextFieldの入力をUILabelへ反映してみます。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
@IBOutlet weak var textField: UITextField!
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
self.textField
.rx
.text
.bind(to: self.label.rx.text) // UILabelに反映
.disposed(by: self.disposeBag) // 不要になったら破棄
}
}
これだけで、UITextField
の入力内容がそのままUILabel
に反映されます。
簡単です!!
既存の方法でやるなら UITextField.addTarget()
にUIControlEvents.EditingChanged
を指定したりして、入力検知をコールバックで受け取ってUILabel
に反映することになるかと思います。
UITextField
が1、2つくらいなら問題ないですが、多いとコールバックが複数、もしくはコールバック内での分岐が増え分かりにくくなりますが、Rxだと繋がりが直感的に分かって良さげです。
UILabelへの反映前に処理をする
上記では、UITextField
への入力内容をそのままUILabel
へ反映しましたが、反映前に処理を追加して反映内容を操作することもできるようです。
例えば、以下のようにすると文字数をカウントしその内容をUILabel
に反映できます。
self.textField
.rx
.text
.map({ "文字数:\(($0 ?? "").count)" }) // 文字数をカウント
.bind(to: self.label.rx.text)
.disposed(by: self.disposeBag)
入力内容のバリデーションなどをリアルタイムで実行したり、色々とできそうです。
.disposed(by:)について
bind(to:)
を呼び出した後には、適切に解除しないとメモリリークに繋がってしまいます。
その為の解除処理が、.disposed(by: self.disposeBag)
になります。
これを行うことで、self.disposeBag
が破棄されるタイミングで同時に解除されます。
それ以外にも以下のようにすると、任意のタイミングで解除することもできます。
let disposable = self.textField
.rx
.text
.bind(to: self.label.rx.text)
// 任意のタイミングで実施する
disposable.dispose()
これだと解除のタイミングを全て管理する必要があり漏れる可能性もありますので、基本的には、.disposed(by:)
を使った方が良さげです。
モデルとのバインド
UI同士のバインドはでしたが、今度はモデルとUIとのバインドのサンプルです。
import UIKit
import RxSwift
import RxCocoa
struct Model {
let item = BehaviorRelay<String?>(value: "Hello!")
}
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
@IBOutlet weak var textField: UITextField!
private var model = Model()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// UITextField -> Model.itemをバインド
self.textField
.rx
.text
.bind(to: self.model.item)
.disposed(by: self.disposeBag)
// Model.item -> UILabelをバインド
self.model.item
.bind(to: self.label.rx.text)
.disposed(by: self.disposeBag)
}
}
これで、UITextField
に入力された値がModel.item
に反映され、その後にUILabel
にも反映されます。
最後に
今回触った範囲は、RxSwiftの初歩中の初歩です。
まだまだ色々とできそうなので、触った所感を記事にできたらなと思います。