はじめに
Swift で iOS アプリを実装し直すにあたって、
ただ Swift に翻訳するだけではつまらないので、
MVVM アーキテクチャで実装することに決めた。
View/ViewController と ViewModel のバインド、
イベントや非同期 API 呼び出しのインタフェースを統一するために、
ReactiveCocoa を導入する。
CocoaPods で RactiveCocoa をインストール
Podfile に
pod "ReactiveCocoa"
を追加して pod install
。
Bridging-Header で ReactiveCocoa のヘッダーファイルをインポート
<プロジェクト名>-Bridging-Header.h に次の行を追加。
#import <ReactiveCocoa/ReactiveCocoa.h>
これで Swift から ReactiveCocoa が使えるようになった。
RACSignal を作成してみる
API 非同期呼び出しのインタフェースを RACSignal で統一したいので、RACSignal でラップする。
class CommentViewModel: NSObject {
var ownerName: String!
var repoName: String!
var issueId: Int!
var comment: String?
func postComment() -> RACSignal {
return RACSignal.createSignal({ (subscriber) -> RACDisposable! in
let manager = AFHTTPRequestOperationManager.manager()
let operation = manager.POST(
"/repositories/¥(self.ownerName)/¥(self.repoName)/issues/¥(self.issueId)/comments",
parameters: ["comment": self.comment],
success: { (operation, response) -> Void in
subscriber.sendNext(response)
subscriber.sendCompleted()
},
failure: { (operation, error) -> Void in
subscriber.sendError(error)
}
)
return RACDisposable(block: { () -> Void in
operation.cancel()
})
})
}
}
UITextView に ViewModel をバインドしてみる
UITextView の text に変更があったら即座に ViewModel に反映させてみる。
Objective-C では RAC マクロを使ってバインドできるけど、Swift では RAC マクロが使えないので、
低レイヤーのメソッドを使うしかなかった。
self.commentTextView.rac_textSignal().setKeyPath("comment", onObject: self.viewModel)
ちなみに、同じことが UITextField でも可能。
ViewModel のプロパティを UIBarButtonItem にバインドしてみる
例えば、「ViewModel の comment プロパティが空ではないとき UIBarButtonItem を有効にする」
というのを ReactiveCocoa で実装するとこんな感じ。
self.viewModel.rac_valuesForKeyPath(
"comment",
observer: self.viewModel
).map({ (next) -> AnyObject! in
let newComment = next as String
return !newComment.isEmpty
}).setKeyPath("enabled", onObject: self.postButtonItem)
RACCommand を使って ViewModel のメソッドも UIBarButtonItem にバインドしてみる
「ViewModel の comment プロパティが空ではないとき UIBarButtonItem を有効にする」だけでなく、
UIBarButtonItem をタップしたときのアクションも併せて ReactiveCocoa で実装。
let commentSignal = self.viewModel.rac_valuesForKeyPath(
"comment",
observer: self.viewModel
).map({ (next) -> AnyObject! in
let newComment = next as String
return !newComment.isEmpty
})
let postCommand = RACCommand(
enabled: commentSignal,
signalBlock: { (input) -> RACSignal! in
return self.viewModel.postComment()
}
)
self.postButtonItem.rac_command = postCommand
ReactiveCocoa を使ってみて
インタフェースを RACSignal に統一できるので、コードを書きやすい。
特に RACSignal 化した非同期呼び出しは、引数でコールバックを渡す必要がないのでスッキリする。
ただ、コントロールにバインドする RACSignal や RACCommand は、
ViewModel のプロパティとして実装したほうが良かった。
ReactiveCocoa 難しい。
RACSignal にようやく慣れてきたけど、
RACCommand やその他の機能はまだ理解が追いついていない。