前回の
- Swiftで有限オートマトン(ステートマシン)を作る - Qiita
- Swift+有限オートマトンでPromiseを拡張する - Qiita
- SwiftTask(Promise拡張)を使う - Qiita
の続きです。まだこのネタ引きずってました
Objective-C時代のリアクティブプログラミング用のOSX/iOSフレームワークといえば、ReactiveCocoaがデファクトスタンダードですが、使いこなせると強力な一方で、パッと見、複雑そうで敬遠している方も多いと思います(私もつい最近までそうでした)。
実際に、GitHubのソースを追うのにも一苦労し、RACDisposable
がこれこれこうなって・・・なるほどわからん 状態になることが少なくありません。
そこで今回、Swiftでもっと簡単に、カジュアルにリアクティブプログラミングを実装したいということで、ReactKitというライブラリを作りました。
ReactKit
https://github.com/inamiy/ReactKit
使用例
まずは簡単で分かりやすい KVO/KVCの例を見ていきます。
// create signal via KVO
self.obj1Signal = KVO.signal(obj1, "value")
// bind signal via KVC (<~ as binding operator)
(obj2, "value") <~ self.obj1Signal
XCTAssertEqual(obj1.value, "initial")
XCTAssertEqual(obj2.value, "initial")
obj1.value = "REACT"
XCTAssertEqual(obj1.value, "REACT")
XCTAssertEqual(obj2.value, "REACT")
obj1.value
の変更に合わせて、obj2.value
が自動的に連動しているのが分かります。
Signal
の中身は、実は先日のQiita記事で作ったSwiftTaskを使用していて、Task
のサブクラスという形でthen
-ableな設計になっています。
その代償として、Promise Chaining優先のためにRACDisposableクラス等が省かれており、 bindingを解除する際は、signal
ごと解放します。
self.obj1Signal = nil // release signal & its bindings
obj1.value = "Done"
XCTAssertEqual(obj1.value, "Done")
XCTAssertEqual(obj2.value, "REACT") // not reacting
2014/09/30現在、Signal
としてまともに使えるのは、
- KVO
- NSNotification
- 一部のUIControl
のみですが、その他Foundation/AppKit/UIKitクラスのSignal化も随時行っていきます。
より実践的な例
QiitaにReactiveCocoaのとても良いサンプルがあったので、拝借させていただきました。
4つのUITextField
の入力値とUIButton
のenabled
を連動させる処理をReactKitで書くと、こうなります。
let usernameTextSignal = self.usernameTextField.textChangedSignal()
let emailTextSignal = self.emailTextField.textChangedSignal()
let passwordTextSignal = self.passwordTextField.textChangedSignal()
let password2TextSignal = self.password2TextField.textChangedSignal()
let anyTextSignal = Signal.any([usernameTextSignal, emailTextSignal, passwordTextSignal, password2TextSignal])
// create button-enabling signal via any textField change
self.buttonEnablingSignal = anyTextSignal.map { (values, changedValue) -> NSNumber? in
let username: NSString? = values[0]?
let email: NSString? = values[1]?
let password: NSString? = values[2]?
let password2: NSString? = values[3]?
// validation
let buttonEnabled = username?.length > 0 && email?.length > 0 && password?.length >= MIN_PASSWORD_LENGTH && password? == password2?
// NOTE: use NSNumber because KVO does not understand Bool
return NSNumber(bool: buttonEnabled)
}
// REACT: enable/disable okButton
(self.okButton, "enabled") <~ self.buttonEnablingSignal!
map(f: T -> U)
を使って、anyTextSignal
が変更値として返す(values, changedValue)
tuple型をチェックし、UIButton
のenabled
がKVCで解釈できるNSNumber
に変換しています。
以上、簡単な紹介でしたが、もし良かったらStarやPull Reqをいただけると嬉しいです。