1.ReactiveCocoa簡介
RAC 是一個 iOS 中的函數(shù)式響應式編程框架硕盹,是Github 在開發(fā) GitHub for Mac 過程中的一個副產(chǎn)品,他提供了一系列用來組合和轉(zhuǎn)換值流的 API吝岭,為事件的處理定義了一個標準接口,大大方便了開發(fā)者去管理各種事件的處理陪腌,結(jié)果就是開發(fā)者能從關(guān)注業(yè)務實現(xiàn)的細節(jié)上脫身烹骨,轉(zhuǎn)變?yōu)殚_發(fā)者只需要關(guān)心業(yè)務本身就好了,是不是聽起來很神奇的樣子喧枷,接下來就讓我們進入開發(fā)的另一片天空吧虹统!
2.ReactiveCocoa作用
RAC關(guān)鍵解決的問題是開發(fā)中經(jīng)彻耄回見的“低聚合,高耦合”問題车荔。
在RAC出現(xiàn)之前渡冻,我們編寫iOS代碼,大部分都是在響應一些事件:按鈕點擊忧便、接收網(wǎng)絡消息族吻、屬性變化等等。但處理事件的形式在蘋果官方API中卻有好幾種:如target-action茬腿、代理方法呼奢、KVO、回調(diào)或其它切平。以上這幾種握础,往往在一個項目中基本都會使用到,在不同的地方會出現(xiàn)很多處理事件的形式悴品,這就帶來了不能很好統(tǒng)一管理問題禀综。因此,我們想苔严,有沒有一個統(tǒng)一管理的解決方案呢定枷?這個方案又是怎樣的呢?到這里ReactiveCocoa就該粉墨登場了届氢,它出現(xiàn)的目的就是為了解決統(tǒng)一標準去管理代碼中的事件欠窒。
3.編程思想
ReactiveCocoa結(jié)合了幾種編程風格:
函數(shù)式編程(Functional Programming):使用高階函數(shù),例如函數(shù)用其他函數(shù)作為參數(shù)退子。
響應式編程(Reactive Programming):關(guān)注于數(shù)據(jù)流和變化傳播岖妄。
所以,你可能聽說過ReactiveCocoa被描述為函數(shù)響應式編程(FRP)框架寂祥。
以后使用RAC解決問題荐虐,就不需要考慮調(diào)用順序,直接考慮結(jié)果丸凭,把每一次操作都寫成一系列嵌套的方法中福扬,使代碼高聚合,方便管理惜犀。
4.底層原理
? ? ? 1铛碑、運用的是Hook(鉤子)思想,Hook是一種用于改變API(應用程序編程接口:方法)執(zhí)行結(jié)果的技術(shù).
? ? ? 2虽界、Hook用處:截獲API調(diào)用的技術(shù)亚茬。
? ? ? 3、Hook原理:在每次調(diào)用一個API返回結(jié)果之前浓恳,先執(zhí)行你自己的方法刹缝,改變結(jié)果的輸出。
5.常見類和常見功能
信號(signal)— RACSignal類
本質(zhì):是一種流(流是值的序列化的抽象)
說明:一般表示將來有數(shù)據(jù)傳遞颈将,只要有數(shù)據(jù)改變梢夯,信號內(nèi)部接收到數(shù)據(jù),就會馬上發(fā)出數(shù)據(jù)晴圾。
事件類型:
? ? ? ? next:發(fā)送數(shù)據(jù)到下一個管道
? ? ? ? error:發(fā)送數(shù)據(jù)失敗
? ? ? ? completed:發(fā)送數(shù)據(jù)完成
用法:需要訂閱不同的事件類型才能發(fā)揮作用颂砸,即調(diào)用下面這些實例方法
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock;
- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock;
- (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock;
......
例子:ReactiveCocoa框架使用category來為很多基本UIKit控件添加signal。這樣你就能給控件添加訂閱了死姚,text field的rac_textSignal就是這么來的人乓。
[self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"x:%@",x);
}];
過濾 — Filter
說明:過濾信號,使用它可以獲取滿足條件的信號都毒,舉個形象的比喻就是一張可以自由設置網(wǎng)口大小的漁網(wǎng)色罚,根據(jù)自己需要,對網(wǎng)口進行設置就可以捕到特定規(guī)格的魚账劲。
用法:在用戶登錄時戳护,我們需要關(guān)心用戶名長度是否符合要求,比如要求字符長度超過3瀑焦,那么就可以使用Filter來達到這個目的腌且,如下:
RACSignal *validUsernameSignal =
[self.usernameTextField.rac_textSignal filter:^BOOL(NSString *value) {
return value.length > 3;
}];
映射 — Map
說明:把源信號內(nèi)容映射成新的內(nèi)容净神,簡單點說就是將數(shù)據(jù)改成自己想要的數(shù)據(jù)冗锁。
用法:還是用登錄這個場景,我們需要關(guān)心用戶名長度是否符合要求棱貌,比如字符長度超過3才進行下一步處理禀晓,如下:
RACSignal *usernameLengthSignal =
[[self.usernameTextField.rac_textSignal map:^id(NSString *value) {
return @(value.length);
}];
狀態(tài)推導 — RAC()
說明:用于給某個對象的某個屬性綁定精续。
用法:比如只要文本框文字改變,就會修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;
聚合信號
說明:聚合任意數(shù)量的信號匆绣,然后生成一個新的信號
用法:比如登錄按鈕只有當用戶名和密碼輸入框的輸入都有效時才能進行點擊
// 創(chuàng)建聚合信號
RACSignal *signUpActiveSignal =
[RACSignal combineLatest:@[validUsernameSignal, validPasswordSinal]
reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];
// 綁定按鈕
[signUpActiveSignal subscribeNext:^(NSNumber *signupActive) {
@strongify(self)
self.signInButton.enabled = [signupActive boolValue];
}];
創(chuàng)建信號
說明:創(chuàng)建一個信號驻右,用于執(zhí)行信號中的Block,只要有訂閱者崎淳,Block中的內(nèi)容就會被執(zhí)行堪夭。
用法:比如點擊按鈕進行登錄時,就可以創(chuàng)建一個信號拣凹,然后進行訂閱森爽,然后執(zhí)行登錄的業(yè)務邏輯處理
// 創(chuàng)建信號
- (RACSignal *)signInSignal {
@weakify(self)
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
@strongify(self)
[self.signInService signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success) {
[subscriber sendNext:@(success)];
// 必須創(chuàng)建完成時間
[subscriber sendCompleted];
}];
return nil;
}];
}
// 點擊登錄按鈕
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside]
map:^id(id value) {
@strongify(self)
return [self signInSignal];
}] subscribeNext:^(id x) {
NSLog(@"Sign in result: %@", x);
}];
信號中的信號 — map —> flattenMap
說明:換句話說就是一個外部信號里面還有一個內(nèi)部信號。
用法:比如上面這個栗子嚣镜,訂閱后發(fā)送的數(shù)據(jù) x 類型最終還是信號爬迟,而不是創(chuàng)建信號時發(fā)送的NSNumber類型:
// 點擊登錄按鈕(Map)
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside]
map:^id(id value) {
@strongify(self)
return [self signInSignal];
}] subscribeNext:^(id x) {
NSLog(@"Sign in result: %@", x);// X類型還是RACSignal
}];
?
// 點擊登錄按鈕(FlattenMap)
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside]
flattenMap:^id(id value) {
@strongify(self)
return [self signInSignal];
}] subscribeNext:^(id x) {
NSLog(@"Sign in result: %@", x);// X類型是NSNumber
}];
FlatternMap和Map的區(qū)別:
FlatternMap中的Block返回信號。
Map中的Block返回對象菊匿。
開發(fā)中付呕,如果信號發(fā)出的值不是信號计福,映射一般使用Map
開發(fā)中,如果信號發(fā)出的值是信號徽职,映射一般使用FlatternMap
總結(jié):signalOfsignals用FlatternMap象颖。
添加附加操作(doNext:)
說明:執(zhí)行Next之前,會先執(zhí)行這個Block姆钉,簡單說就是在一段邏輯執(zhí)行前進行攔截说订,然后先執(zhí)行一段特別操作,再操作接下來的邏輯
- (RACSignal *)doNext:(void (^)(id x))block
用法:還是拿登錄的場景來說潮瓶,當?shù)卿泂ervice正在校驗用戶名和密碼時陶冷,登錄按鈕應該是不可點擊的。這會防止用戶多次執(zhí)行登錄操作毯辅。還有埂伦,如果登錄失敗了,用戶再次嘗試登錄時悉罕,應該隱藏錯誤信息赤屋。
6.總結(jié)
ReactiveCocoa的核心就是信號,而它不過就是事件流壁袄,通過訂閱將數(shù)據(jù)發(fā)送出去类早。以上就是本篇筆記的核心概念,哈哈嗜逻,是不是很簡單呢涩僻?
通過以上一些概念和使用場景,我們大概對RAC有了初步的認識栈顷∧嫒眨基礎(chǔ)有了,接下來學習深入的知識點就容易多了萄凤。
參考
1室抽、RayWenderlich ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2