轉自 http://blog.leezhong.com/ios/2013/06/19/frp-reactivecocoa.htmljavascript
Functional Reactive Programming(如下簡稱FRP)是一種響應變化的編程範式。先來看一小段代碼html
a = 2
b = 2
c = a + b // c is 4
b = 3
// now what is the value of c?
若是使用FRP,c
的值將會隨着b
的值改變而改變,因此叫作「響應式編程」。比較直觀的例子就是Excel,當改變某一個單元格的內容時,該單元格相關的計算結果也會隨之改變。java
FRP提供了一種信號機制來實現這樣的效果,經過信號來記錄值的變化。信號能夠被疊加、分割或合併。經過對信號的組合,就不須要去監聽某個值或事件。node
這在重交互的應用裏是很是有用的。以註冊爲例:react
提交按鈕的狀態要跟輸入框的狀態綁定,好比必選的輸入框沒有填完時,提交按鈕是灰色的,也就是不可點;若是提交按鈕不可點,那麼文字變成灰色,否則變成藍色;若是正在提交,那麼輸入框的文字顏色變成灰色,且不可點,否則變成默認色且可點;若是註冊成功就在狀態欄顯示成功信息,否則顯示錯誤信息,等等。ios
能夠看到光是註冊頁就有這麼多的聯動,在javascript中能夠採用事件監聽來處理,iOS中更多的是delegate模式,本質上都是事件的分發和響應。這種作法的缺點是不夠直觀,尤爲在邏輯比較複雜的狀況下。這也是爲何儘管nodejs很高效,但因爲javascript的callback style和異步模式不符合正常的編程習慣,讓不少人望而卻步。git
使用FRP主要有兩個好處:直觀和靈活。直觀的代碼容易編寫、閱讀和維護,靈活的特性便於應對變態的需求。github
ReactiveCocoa是github去年開源的一個項目,是在iOS平臺上對FRP的實現。FRP的核心是信號,信號在ReactiveCocoa(如下簡稱RAC)中是經過RACSignal
來表示的,信號是數據流,能夠被綁定和傳遞。web
能夠把信號想象成水龍頭,只不過裏面不是水,而是玻璃球(value),直徑跟水管的內徑同樣,這樣就能保證玻璃球是依次排列,不會出現並排的狀況(數據都是線性處理的,不會出現併發狀況)。水龍頭的開關默認是關的,除非有了接收方(subscriber),纔會打開。這樣只要有新的玻璃球進來,就會自動傳送給接收方。能夠在水龍頭上加一個過濾嘴(filter),不符合的不讓經過,也能夠加一個改動裝置,把球改變成符合本身的需求(map)。也能夠把多個水龍頭合併成一個新的水龍頭(combineLatest:reduce:),這樣只要其中的一個水龍頭有玻璃球出來,這個新合併的水龍頭就會獲得這個球。objective-c
下面經過一個簡單的demo來演示這個模型。
假如對象的某個屬性想綁定某個消息,可使用RAC
這個宏,至關於給玻璃球套了一個水龍頭。
RAC(self.submitButton.enabled) = [RACSignal combineLatest:@[self.usernameField.rac_textSignal, self.passwordField.rac_textSignal] reduce:^id(NSString *userName, NSString *password) {
return @(userName.length >= 6 && password.length >= 6);
}];
這樣,若是用戶名和密碼框的長度都超過6,提交按鈕就enable了。反之,若是沒符合要求,就會處於非開啓狀態。
能夠看到usernameField
有了一個新的屬性rac_textSignal
,這是RAC在UITextField
category中添加的,直接用便可。
RAC統一了對KVO、UI Event、Network request、Async work的處理,由於它們本質上都是值的變化(Values over time)。
RAC能夠用來監測屬性的改變,這點跟KVO很像,不過使用了block,而不是-observeValueForKeyPath:ofObject:change:context:
[RACAble(self.username) subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];
使用起來是否是比KVO舒服多了。比KVO更增強大的是信號能夠被鏈起來(chain)
// 只有當名字以'j'開頭,纔會被記錄
[[RACAble(self.username)
filter:^(NSString *newName) {
return [newName hasPrefix:@"j"];
}]
subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];
RAC還爲系統UI提供了不少category,來方便消息的建立和傳遞,好比按鈕被點擊或文本框有改動等等,上面的例子中self.firstNameField.rac_textSignal
,在對應的文本框有改動時,會自動向數據流中添加新的數據,綁定該消息的其餘消息就會收到新的數據,若是有subscriber的話,會自動觸發。
這些能夠經過自定義信號,也就是RACSubject
(繼承自RACSignal
,能夠理解爲自由度更高的signal)來搞定。好比一個異步網絡操做,能夠返回一個subject,而後將這個subject綁定到一個subscriber或另外一個信號。
- (void)doTest
{
RACSubject *subject = [self doRequest];
[subject subscribeNext:^(NSString *value){
NSLog(@"value:%@", value);
}];
}
- (RACSubject *)doRequest
{
RACSubject *subject = [RACSubject subject];
// 模擬2秒後獲得請求內容
// 只觸發1次
// 儘管subscribeNext什麼也沒作,但若是沒有的話map是不會執行的
// subscribeNext就是定義了一個接收體
[[[[RACSignal interval:2] take:1] map:^id(id _){
// the value is from url request
NSString *value = @"content fetched from web";
[subject sendNext:value];
return nil;
}] subscribeNext:^(id _){}];
return subject;
}
簡單畫了下關係圖,羅列了些要點
上面只是大概說了一下RAC的使用情景和用法,更多的例子能夠到項目主頁中查看。