[swift] swiftでReactiveCocoa

2015/01/10

こんにちは。きんくまです。

前回に引き続き、Functional Reactive Programming = FRP のライブラリを試してみました。
今回はiOSのライブラリのReactiveCocoaです。

>> ReactiveCocoa/ReactiveCocoa · GitHub

RactiveCocoaはreadmeによると、Microsoftの.NetのReactive Extensions (Rx)を元に作られています。

で、良いチュートリアルがあったので、今回はそれをObjective-Cではなくswiftでやってみました。

>> ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 (by Colin Eberhardt)

ただ、swiftでやっていたところ、所々わからないところがあり、さらに検索してみたところ既に同じことをやられている方がいました。かなり助けられました。

>> A SWIFT REACTION (by Yusef Napora)

どんなデモアプリ?

名前の入力欄があり、サインインボタンはまだ押すことはできません。

rac_demo1_fig1

入力文字数が3文字を越えるとテキストフィールドの背景が白になります。
また、名前と名字の両方ともがそうなると、サインインボタンが押せるようになります。

rac_demo1_fig2

ボタンを押すと、ダミーの非同期処理の照会が走って

rac_demo1_fig3

うまくいくと、次の画面にすすみます。オリジナルのチュートリアルでは猫ちゃんだったのですが、ここでは文字を表示しています。

rac_demo1_fig4

ソースコード

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var usernameTextFieid:UITextField!
    @IBOutlet weak var passwordTextFieid:UITextField!
    @IBOutlet weak var signInButton:UIButton!
    @IBOutlet weak var loadingIndicator:UIActivityIndicatorView!
    @IBOutlet weak var errorLabel:UILabel!
    var signInService:RWDummySignInService!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.signInService = RWDummySignInService()
        self.errorLabel.hidden = true
        self.loadingIndicator.hidden = true
        
        let validUsernameSignal:RACSignal = self.usernameTextFieid.rac_textSignal()
            .map { (value:AnyObject!) -> AnyObject! in
                return self.isValidnUsername(value as NSString)
            }
        
        let validPasswordSignal:RACSignal = self.passwordTextFieid.rac_textSignal()
            .map { (value:AnyObject!) -> AnyObject! in
                return self.isValidPassword(value as NSString)
            }
        
        validUsernameSignal
            .map { (value:AnyObject!) -> AnyObject! in
                return (value as NSNumber).boolValue ? UIColor.clearColor() : UIColor.yellowColor()
            }
            .subscribeNext { (value:AnyObject!) -> Void in
                self.usernameTextFieid.backgroundColor = (value as UIColor)
            }
        
        validPasswordSignal
            .map { (value:AnyObject!) -> AnyObject! in
                (value as NSNumber).boolValue ? UIColor.clearColor() : UIColor.yellowColor()
            }
            .subscribeNext { (value:AnyObject!) -> Void in
                self.passwordTextFieid.backgroundColor = (value as UIColor)
            }
        
        let signUpActiveSignal:RACSignal = RACSignal
            .combineLatest([validUsernameSignal, validPasswordSignal])
            .and()
        signUpActiveSignal.subscribeNext { (value:AnyObject!) -> Void in
            self.signInButton.enabled = (value as NSNumber).boolValue
        }
        
        self.signInButton.rac_signalForControlEvents(UIControlEvents.TouchUpInside)
            .doNext { (x:AnyObject!) -> Void in
                self.signInButton.enabled = false
                self.errorLabel.hidden = true
                self.loadingIndicator.hidden = false
                self.loadingIndicator.startAnimating()
            }
            .flattenMap { (x:AnyObject!) -> RACStream! in
                return self.signInSignal()
            }
            .subscribeNext { (value:AnyObject!) -> Void in
                self.loadingIndicator.stopAnimating()
                let success = (value as NSNumber).boolValue
                if(success){
                    self.performSegueWithIdentifier("signInSuccess", sender: self)
                }else{
                    self.errorLabel.hidden = false
                }
                self.signInButton.enabled = true
                self.loadingIndicator.hidden = true
            }
    }
    
    private func isValidnUsername(username:NSString) -> NSNumber{
        return NSNumber(bool:(username.length > 3))
    }
    
    private func isValidPassword(password:NSString) -> NSNumber{
        return NSNumber(bool:(password.length > 3))
    }
    
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        usernameTextFieid.resignFirstResponder()
        passwordTextFieid.resignFirstResponder()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func signInSignal()->RACSignal{
        return RACSignal.createSignal{
            (subscriber:RACSubscriber!) -> RACDisposable! in
            self.signInService.signInWithUsername(self.usernameTextFieid.text,
                password: self.passwordTextFieid.text,
                complete: { (success:Bool) -> Void in
                    subscriber.sendNext(NSNumber(bool:success))
                    subscriber.sendCompleted()
            })
            return nil
        }
    }
}

>> プロジェクトデータ

上のプロジェクトではCocoaPodsは使わず手動でライブラリを入れてあります。

感想

書いてみた感じでは、前回のbacon.jsと同様 データの流れがあって、それの値を読み取ったり、流れを合流させたりといった感じ。たぶんこの感じがFRPなんだと思われ。

あとObjective-Cのコードだと、 [ ] = square braket がやたらと入れ子になっていて見づらいのですが、swiftだとそれがないので見やすい&書きやすいと思いました。

ちょっと気をつけたいことは、

・最新版のライブラリがiOS8以降対応(iOS7だとダメ)
・開発中のバージョン3.0ではいろいろと代わりそう

ということでしょうか。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る