AVCam-iOSをReactiveCocoa4で実装し直してみる



### 開発環境

+ XCode7.3
+ Swift2.2
+ ReactiveCocoa4

### Reactiveとは

ここが分かりやすかった
[古くて新しいリアクティブプログラミングのすすめ-Frontrend Conference](https://html5experts.jp/ahomu/13333/)

最初に紹介したReactiveについての解説よりは実装的な解説があって分かりやすい。ObjC、SwiftではなくRxJSというJavaScriptのフレームワークを使って解説しているけど。

[「RxJS」初心者入門 – JavaScriptの非同期処理の常識を変えるライブラリ](http://liginc.co.jp/web/js/151272)



結論としては、MVVMというアーキテクチャを実装するのに使われるのがReactive・・・で合ってるかな？


### iOSアプリで実装するには

選択肢は大雑把には二つ

+ ReactiveCocoa
+ RxSwift

RxSwiftより前に知ってたので、ReactiveCocoaを選択。


### ReactiveCocoaとは

[ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa)

iOSアプリ開発界では先発組だったけど、ObjCからSwiftへの転換は比較的最近。なので資料(googleでの検索結果)が圧倒的にObjC時代のが多く、今のSwiftでのやり方がすぐに見つからない場合もあります。


### 導入方法

手動、Pods install、Carthageと色々あるけど今回はCarthageにしました。
Pods installと似てるけど、Carthageの方がプロジェクトに対する影響が小さいようです。こちらの方が好みですね

Carthageの具体的な使い方は以下を参照
[Carthageを使ってビルド時間を短縮しよう](http://qiita.com/yutat93/items/97fe9bc2bf2e97da7ec1)
[ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) <- ReadMeにやり方載ってます


### 実装

お手頃なのが無かったので、とりあえず先日作ったAVCam-iOSをReactiveCocoaを使って作り直してみました。
先に結論を言っておくと、あまり適当な例では無かったw
作ったものはgithubにあげてあります。

[AVCamRCSample](https://github.com/rnsm504/AVCamRCSample)


やり方がすぐにわからなかったのをピックアップ。
例えば以下のようなボタンのタップに対するActionをReactiveCocoaで実装しようとすると

```CameraViewController.swift

    @IBAction func resumeInterruptedSession(sender: AnyObject) {
        dispatch_async(self.sessionQueue, {
            self.session.startRunning()
            self.sessionRunning = self.session.running
            if(!self.session.running){
                dispatch_async(dispatch_get_main_queue(), {
                    let message = NSLocalizedString("Unable to resume", comment: "Alert message when unable to resume the session running")
                    let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle:.Alert)
                    let cancleAction = UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .Cancel, handler: nil)
                    alertController.addAction(cancleAction)
                    self.presentViewController(alertController, animated: true, completion: nil)
                })
            } else {
                dispatch_async(dispatch_get_main_queue(), {
                    self.resumeButton.hidden = true
                })
            }
        })
    }

```

こういう形になる

```CameraViewController.swift

        let resumeInterruptedSessionAction = Action<AnyObject, Void, NSError>{ _ in
            dispatch_async(self.sessionQueue, {
                self.session.startRunning()
                self.sessionRunning = self.session.running
                if(!self.session.running){
                    dispatch_async(dispatch_get_main_queue(), {
                        let message = NSLocalizedString("Unable to resume", comment: "Alert message when unable to resume the session running")
                        let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle:.Alert)
                        let cancleAction = UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .Cancel, handler: nil)
                        alertController.addAction(cancleAction)
                        self.presentViewController(alertController, animated: true, completion: nil)
                    })
                } else {
                    dispatch_async(dispatch_get_main_queue(), {
                        self.resumeButton.hidden = true
                    })
                }
            })
            
            return SignalProducer.empty
        }
        
        resumeButton.addTarget(resumeInterruptedSessionAction.unsafeCocoaAction, action: CocoaAction.selector, forControlEvents: .TouchUpInside)
      

```


ジェスチャーの場合は以下のようにしてたものを

```CameraViewController.swift
    @IBAction func focusAndExposeTap(gestureRecognizer: UITapGestureRecognizer) {
        let devicePoint = (self.previewView.layer as! AVCaptureVideoPreviewLayer).captureDevicePointOfInterestForPoint(gestureRecognizer.locationInView(gestureRecognizer.view))
        self.focusWithMode(AVCaptureFocusMode.AutoFocus, exposeWithMode: AVCaptureExposureMode.AutoExpose, atDevicePoint: devicePoint, motiorSubjectAreaChange: true)
    }
    
```

このように変更する

```CameraViewController.swift
        let gesRect = UITapGestureRecognizer()
        self.previewView.addGestureRecognizer(gesRect)
        gesRect.rac_gestureSignal().toSignalProducer().map({ (x) -> CGPoint in
            return (self.previewView.layer as! AVCaptureVideoPreviewLayer).captureDevicePointOfInterestForPoint(x!.locationInView(x!.view))
        }).startWithNext { (pointInView) -> () in
            self.focusWithMode(AVCaptureFocusMode.AutoFocus, exposeWithMode: AVCaptureExposureMode.AutoExpose, atDevicePoint: pointInView, motiorSubjectAreaChange: true)
        }
```


### 結論

まあお試しなんで良いも悪いもないのですが、今回のサンプルではまだReactiveのメリットを試しきれてないので、もう少し恩恵を受けやすい形のサンプルを用意して試してみたいと思います。

### 追記

参考になるサイト

[ReactiveCocoa勉強会関西を開催しました #rac_kansai](http://ninjinkun.hatenablog.com/entry/2014/08/03/204348)

[ReactiveCocoa and MVVM, an Introduction](http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/)