ReactiveCocoa簡(jiǎn)介
ReactiveCocoa(簡(jiǎn)稱為RAC),是由Github開(kāi)源的一個(gè)應(yīng)用于iOS和OS開(kāi)發(fā)的新框架,Cocoa是蘋(píng)果整套框架的簡(jiǎn)稱,因此很多蘋(píng)果框架喜歡以Cocoa結(jié)尾。
ReactiveCocoa作用
在我們iOS開(kāi)發(fā)過(guò)程中,當(dāng)某些事件響應(yīng)的時(shí)候,需要處理某些業(yè)務(wù)邏輯,這些事件都用不同的方式來(lái)處理。
比如按鈕的點(diǎn)擊使用action,ScrollView滾動(dòng)使用delegate,屬性值改變使用KVO等系統(tǒng)提供的方式。
其實(shí)這些事件,都可以通過(guò)RAC處理
ReactiveCocoa為事件提供了很多處理方法,而且利用RAC處理事件很方便,可以把要處理的事情,和監(jiān)聽(tīng)的事情的代碼放在一起,這樣非常方便我們管理,就不需要跳到對(duì)應(yīng)的方法里。非常符合我們開(kāi)發(fā)中高聚合,低耦合的思想。
編程思想
在開(kāi)發(fā)中我們也不能太依賴于某個(gè)框架,否則這個(gè)框架不更新了,導(dǎo)致項(xiàng)目后期沒(méi)辦法維護(hù),比如之前Facebook提供的Three20框架,在當(dāng)時(shí)也是神器,但是后來(lái)不更新了,也就沒(méi)什么人用了。因此我感覺(jué)學(xué)習(xí)一個(gè)框架,還是有必要了解它的編程思想。
先簡(jiǎn)單介紹下目前咱們已知的編程思想。
3.1 面向過(guò)程:處理事情以過(guò)程為核心,一步一步的實(shí)現(xiàn)。
3.2 面向?qū)ο螅喝f(wàn)物皆對(duì)象
3.3 鏈?zhǔn)骄幊趟枷耄菏菍⒍鄠€(gè)操作(多行代碼)通過(guò)點(diǎn)號(hào)(.)鏈接在一起成為一句代碼,使代碼可讀性好。a(1).b(2).c(3)
? ? ? ?鏈?zhǔn)骄幊烫攸c(diǎn):方法的返回值是block,block必須有返回值(本身對(duì)象),block參數(shù)(需要操作的值)
? ? ? ?代表:masonry框架。
3.4 響應(yīng)式編程思想:不需要考慮調(diào)用順序,只需要知道考慮結(jié)果,類似于蝴蝶效應(yīng),產(chǎn)生一個(gè)事件,會(huì)影響很多東西,這些事件像流一樣的傳播出去,然后影響結(jié)果,借用面向?qū)ο蟮囊痪湓?,萬(wàn)物皆是流。
? ? ? ?代表:KVO運(yùn)用。
3.5 函數(shù)式編程思想:是把操作盡量寫(xiě)成一系列嵌套的函數(shù)或者方法調(diào)用。
? ? ? ?函數(shù)式編程特點(diǎn):每個(gè)方法必須有返回值(本身對(duì)象),把函數(shù)或者Block當(dāng)做參數(shù),block參數(shù)(需要操作的值)block返回值(操作結(jié)果)
? ? ? ?代表:ReactiveCocoa。
ReactiveCocoa結(jié)合了幾種編程風(fēng)格:
函數(shù)式編程(Functional Programming)
響應(yīng)式編程(Reactive Programming)
所以,你可能聽(tīng)說(shuō)過(guò)ReactiveCocoa被描述為函數(shù)響應(yīng)式編程(FRP)框架。
以后使用RAC解決問(wèn)題,就不需要考慮調(diào)用順序,直接考慮結(jié)果,把每一次操作都寫(xiě)成一系列嵌套的方法中,使代碼高聚合,方便管理。
ReactiveCocoa的簡(jiǎn)單使用
???假設(shè)我們現(xiàn)在有個(gè)登錄界面,需要輸入username和password,然后點(diǎn)擊signinButton,就能發(fā)起登錄請(qǐng)求
[[[self.usernameTextField.rac_textSignal
??map:^id(NSString*text){
? ? return @(text.length);
??filter:^BOOL(NSNumber*length){
? ? return [length integerValue] > 3;
??subscribeNext:^(id x){
? ? NSLog(@"%@", x);
??}];
代碼解析:這里給username編輯框的事件添加信號(hào)跟蹤,將其映射為文本長(zhǎng)度,超出長(zhǎng)度3的輸出日志。而且ReactiveCocoa的方法特性是都會(huì)返回類對(duì)象自身,方便鏈?zhǔn)秸{(diào)用。
插入介紹一下名詞解釋
信號(hào)(signal)— RACSignal類
本質(zhì):是一種流(流是值的序列化的抽象)
說(shuō)明:一般表示將來(lái)有數(shù)據(jù)傳遞,只要有數(shù)據(jù)改變,信號(hào)內(nèi)部接收到數(shù)據(jù),就會(huì)馬上發(fā)出數(shù)據(jù)。
事件類型:
? ?? ???next:發(fā)送數(shù)據(jù)到下一個(gè)管道
? ?? ???error:發(fā)送數(shù)據(jù)失敗
? ?? ???completed:發(fā)送數(shù)據(jù)完成
用法:需要訂閱不同的事件類型才能發(fā)揮作用,即調(diào)用下面這些實(shí)例方法
- (RACDisposable *)subscribeNext: (void (^)(id x))nextBlock;
- (RACDisposable *)subscribeError: (void (^)(NSError *error))errorBlock;
- (RACDisposable *)subscribeCompleted: (void (^)(void))completedBlock;
例子:ReactiveCocoa框架使用category來(lái)為很多基本UIKit控件添加signal。這樣你就能給控件添加訂閱了,text field的rac_textSignal就是這么來(lái)的。
[self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"x:%@",x);
}];
過(guò)濾?— Filter
說(shuō)明:過(guò)濾信號(hào),使用它可以獲取滿足條件的信號(hào),舉個(gè)形象的比喻就是一張可以自由設(shè)置網(wǎng)口大小的漁網(wǎng),根據(jù)自己需要,對(duì)網(wǎng)口進(jìn)行設(shè)置就可以捕到特定規(guī)格的魚(yú)。
用法:在用戶登錄時(shí),我們需要關(guān)心用戶名長(zhǎng)度是否符合要求,比如要求字符長(zhǎng)度超過(guò)3,那么就可以使用Filter來(lái)達(dá)到這個(gè)目的,如下:
RACSignal *validUsernameSignal =
[self.usernameTextField.rac_textSignal filter:^BOOL(NSString *value) {
return value.length > 3;
}];
映射?— Map
說(shuō)明:把源信號(hào)內(nèi)容映射成新的內(nèi)容,簡(jiǎn)單點(diǎn)說(shuō)就是將數(shù)據(jù)改成自己想要的數(shù)據(jù)。
用法:還是用登錄這個(gè)場(chǎng)景,我們需要關(guān)心用戶名長(zhǎng)度是否符合要求,比如字符長(zhǎng)度超過(guò)3才進(jìn)行下一步處理,如下:
RACSignal *usernameLengthSignal =
[[self.usernameTextField.rac_textSignal map:^id(NSString *value) {
return @(value.length);
}];
狀態(tài)推導(dǎo)?— RAC()
說(shuō)明:用于給某個(gè)對(duì)象的某個(gè)屬性綁定。
用法:比如只要編輯框文字改變,就會(huì)修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;
聚合信號(hào)
說(shuō)明:聚合任意數(shù)量的信號(hào),然后生成一個(gè)新的信號(hào)
用法:比如登錄按鈕只有當(dāng)用戶名和密碼輸入框的輸入都有效時(shí)才能進(jìn)行點(diǎn)擊
// 創(chuàng)建聚合信號(hào)
RACSignal *signUpActiveSignal =
[RACSignal combineLatest: @[validUsernameSignal, validPasswordSinal]
reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];
參照下圖,來(lái)控制界面對(duì)于用戶操作的響應(yīng)。
- (RACSignal *)signInSignal {
return[RACSignal createSignal:^RACDisposable *(id subscriber){
? ?[self.signInService?
? ???signInWithUsername:self.usernameTextField.text
? ?? ?? ?? ?? ?password:self.passwordTextField.text
? ?? ?? ?? ?? ?complete:^(BOOL success){
? ?? ?? ?? ?? ?? ???[subscriber sendNext: @(success)];
? ?? ?? ?? ?? ?? ???[subscriber sendCompleted];
? ???}];
returnnil;
}];
}
上面的方法創(chuàng)建了一個(gè)信號(hào),使用用戶名和密碼登錄并調(diào)用接口驗(yàn)證用戶?,F(xiàn)在分解來(lái)看一下。
上面的代碼使用RACSignal的createSignal:方法來(lái)創(chuàng)建信號(hào)。方法的入?yún)⑹且粋€(gè)block,這個(gè)block描述了這個(gè)信號(hào)。當(dāng)這個(gè)信號(hào)有subscriber時(shí),block里的代碼就會(huì)執(zhí)行。
block的入?yún)⑹且粋€(gè)subscriber實(shí)例,它遵循RACSubscriber協(xié)議,協(xié)議里有一些方法來(lái)產(chǎn)生事件,你可以發(fā)送任意數(shù)量的next事件,或者用error\complete事件來(lái)終止。本例中,信號(hào)發(fā)送了一個(gè)next事件來(lái)表示登錄是否成功,隨后是一個(gè)complete事件。
這個(gè)block的返回值是一個(gè)RACDisposable對(duì)象,它允許你在一個(gè)訂閱被取消時(shí)執(zhí)行一些清理工作。當(dāng)前的信號(hào)不需要執(zhí)行清理操作,所以返回nil就可以了。
可以看到,把一個(gè)異步API用信號(hào)封裝是多簡(jiǎn)單!? ?? ?
外部訂閱信號(hào)時(shí),代碼可以這樣寫(xiě):
[[[[self.signInButton
? ?doNext:^(id x){
? ???self.signInButton.enabled =NO;
? ???self.signInFailureText.hidden =YES;
? ?flattenMap:^id(id x){
? ???return[self signInSignal];
? ?subscribeNext:^(NSNumber*signedIn){
? ???self.signInButton.enabled =YES;
? ???BOOL success =[signedIn boolValue];
? ???self.signInFailureText.hidden = success;
? ???if(success){
? ?? ? [self performSegueWithIdentifier: @"signInSuccess" sender:self];
? ???}
? ?}];
因?yàn)樯厦娣椒▌?chuàng)建的是內(nèi)部信號(hào),需要使用flattenMap轉(zhuǎn)換一下??梢钥吹絛oNext:是直接跟在按鈕點(diǎn)擊事件的后面。而且doNext: block并沒(méi)有返回值。因?yàn)樗歉郊硬僮鳎⒉桓淖兪录旧?。整個(gè)流程圖如下:?