前幾個(gè)月一直在學(xué)習(xí)RxSwift券犁,確實(shí)相當(dāng)酷的一個(gè)開源庫茧妒,受益匪淺姿骏。在未來學(xué)習(xí)swift版本(ReactiveSwift)RAC(ReactiveCocoa)之前特意花了3天回顧了一下OC版本(ReactiveObjC)身笤。而之所以愿意寫下本篇豹悬,是因?yàn)檫@3天中有1天半是在仔細(xì)閱讀官方文檔。官方文檔理解之后再去看之前看過的一些他人寫的博客液荸,發(fā)現(xiàn)質(zhì)量良莠不齊瞻佛,真正值得一讀的屈指可數(shù)。不禁想到原來居然走了那么多彎路。萬維剛說:只有學(xué)習(xí)了“學(xué)習(xí)的方法”之后才能快速進(jìn)步伤柄。所以學(xué)會(huì)了哪個(gè)開源庫不重要绊困,重要的是怎么學(xué)會(huì)的。越是復(fù)雜的開源庫适刀,越是要仔細(xì)閱讀官方文檔秤朗,之后遇到困惑的地方再找博客對比查證一番,事半功倍笔喉。
言歸正傳取视,本篇文章主要分為三個(gè)部分:ReactiveObjC簡介,ReactiveObjC中的基本概念與簡單使用常挚,ReactiveObjC中豐富而神奇的操作符贫途。
按照慣例,先來一張圖鎮(zhèn)帖待侵。
繼續(xù)閱讀之前丢早,強(qiáng)烈建議讀者先去了解或者重溫一遍官方文檔Introduction?和Documentation?,對于接下來的理解會(huì)很有幫助秧倾。
ReactiveObjC簡介
ReactiveCocoa-簡稱為RAC怨酝,現(xiàn)在可分為OC版本-ReactiveObjC和swift版本-ReactiveSwift。本篇文章僅介紹ReactiveObjC那先,之后會(huì)有介紹ReactiveSwift的节预。
RAC是一個(gè)將函數(shù)響應(yīng)式編程范式帶入iOS的開源庫格了,其兼具函數(shù)式與響應(yīng)式的特性伟众。它是由Josh Abernathy和Justin Spahr-Summers當(dāng)初在開發(fā)GitHub for Mac?過程中創(chuàng)造的系瓢,靈感來源于Functional Reactive Programming?。所以揖闸,這么一個(gè)神奇?zhèn)ゴ蟮膸熳岫椋谷皇莻€(gè)副產(chǎn)物!而這個(gè)副產(chǎn)物比孕育它的產(chǎn)品出名的多汤纸,不得不說很有意思衩茸。
那么問題來了,什么是函數(shù)響應(yīng)式編程-簡稱為FRP 呢贮泞?一言以蔽之楞慈,F(xiàn)RP是基于異步事件流進(jìn)行編程的一種編程范式。針對離散事件序列進(jìn)行有效的封裝啃擦,利用函數(shù)式編程的思想囊蓝,滿足響應(yīng)式編程的需要。
網(wǎng)上資料一大堆令蛉,這里就不多介紹了聚霜,重點(diǎn)說一下我個(gè)人的理解。
函數(shù)式編程
舉一個(gè)簡單的??:
已知:`f(x) = 2sin(x + π/2) + 3`, 求 `f(π/2)`的值俯萎。
怎么做呢,把 `x = π/2` 就可以得出答案运杭,so easy夫啊。
那如果是函數(shù)式做法呢?
首先定義如下幾個(gè)函數(shù):
`f1(x) = x` ;
`f2(x) = x + π/2` ;
`f3(x) = sin(x)` ;
`f4(x) = 2x` ;
`f5(x) = x + 3` ;
然后將最初的`f(x)`改寫成`f(x) = f5(f4(f3(f2(f1(x)))))`辆憔。
也就是說撇眯,將每一個(gè)復(fù)雜的問題都設(shè)計(jì)成一個(gè)高階函數(shù),其中的參數(shù)又是一個(gè)新的函數(shù)虱咧,以此類推熊榛。有點(diǎn)類似陳凱歌電影《無極》里面的 “圓環(huán)套圓環(huán)”。
其中每一個(gè)函數(shù)都是穩(wěn)定無副作用的腕巡,表現(xiàn)在:在任意時(shí)刻輸入相同的值玄坦,內(nèi)部經(jīng)過運(yùn)算后都會(huì)輸出相同的值,不會(huì)對外界產(chǎn)生任何影響绘沉。
上個(gè)月看了幾頁王東岳的《物演通論》煎楣,驚為天書,雖然幾乎沒看懂神馬车伞,但是也不是一無所獲择懂。“尺度” 是一個(gè)看待問題非常重要的點(diǎn)另玖。同一個(gè)問題應(yīng)用不同尺度可能得出的結(jié)論天壤之別困曙。
從時(shí)間角度看,朝夕是一種尺度谦去,一萬年是另一種尺度慷丽,幾億年是第三種尺度;從空間角度看鳄哭,微觀是一種尺度盈魁,宏觀是另一種尺度。
程序員追求將代碼寫得結(jié)構(gòu)清晰窃诉,邏輯合理杨耙,很大一部分原因是為了能夠高效“復(fù)用”。面向?qū)ο缶幊炭蓮?fù)用的“尺度”是“類”級別的飘痛,而函數(shù)式編程可復(fù)用的尺度是“函數(shù)”級別的珊膜。
響應(yīng)式編程
響應(yīng)式編程?是一種面向數(shù)據(jù)流和變化傳播的編程范式。這意味著可以在編程語言中很方便地表達(dá)靜態(tài)或動(dòng)態(tài)的數(shù)據(jù)流宣脉,而相關(guān)的計(jì)算模型會(huì)自動(dòng)將變化的值通過數(shù)據(jù)流進(jìn)行傳播车柠。
來看下面一小段代碼:
NSInteger a = 3;
NSInteger b = 4;
NSInteger c = a + b;
NSLog(@"c is %ld",c);
a = 5;
b = 6;
NSLog(@"c is %ld",c);
初始化c時(shí)其值等于a和b的總和,當(dāng)a或者b或者a與b同時(shí)改變時(shí),若想讓c的值仍然等于a和b的總和竹祷,若是命令式代碼需要重寫一遍`c = a + b`谈跛;而若是響應(yīng)式則完全不需要。
iOS中其實(shí)也有響應(yīng)式編程的典型例子:Autolayout塑陵。
舉個(gè)實(shí)際些的??:
為了實(shí)現(xiàn)在注冊頁面注冊按鈕`enable`狀態(tài)由幾個(gè)`textField`的文本內(nèi)容決定這么一個(gè)小需求感憾。
應(yīng)用命令式寫法類似如下:
- (void)dealloc {
? ? [[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeRegisterButtonEnableState) name:UITextFieldTextDidChangeNotification object:nil];
}
- (void)changeRegisterButtonEnableState {
? ? self.registerButton.enabled = [self isInfomationValid];
}
- (BOOL)isInfomationValid {
? ? return self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0 && [self.passwordTextField.text isEqualToString:self.confirmTextField.text];
}
可以看出,此時(shí)一個(gè)完整的邏輯會(huì)被分散到多個(gè)方法中令花,散亂的分布在各個(gè)位置阻桅。
應(yīng)用RAC響應(yīng)式寫法類似如下:
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? RACSignal *validSignal = [RACSignal
? ? combineLatest:@[self.usernameTextField.rac_textSignal,
? ? ? ? ? ? ? ? ? ? self.passwordTextField.rac_textSignal,
? ? ? ? ? ? ? ? ? ? self.confirmTextField.rac_textSignal]
? ? reduce:^(NSString *username, NSString *password, NSString *confirm){
? ? ? ? return @(username.length > 0 && password.length > 0 && [password isEqualToString:confirm]);
? ? }];
? ? RAC(self.registerButton, enabled) = validSignal;
}
可以看出,此時(shí)一個(gè)完整的邏輯會(huì)被聚合在一塊兒兼都,非常清晰嫂沉,可讀性也非常強(qiáng)。
至此扮碧,如果以上這些內(nèi)容對你并沒有什么吸引力趟章,可以不繼續(xù)往下看了。
ReactiveObjC中的基本概念與簡單使用
如果讓我用一句話總結(jié)RAC到底能干嘛慎王?那就是:統(tǒng)一所有異步事件的回調(diào)方式尤揣。
大一統(tǒng)
作為iOS開發(fā)者,我們寫的絕大部分代碼其實(shí)都是為了響應(yīng)事件發(fā)生或者狀態(tài)變化:當(dāng)一個(gè)按鈕被點(diǎn)擊時(shí)柬祠,需要寫一個(gè)`@IBAction`方法來響應(yīng)北戏;當(dāng)需要監(jiān)聽鍵盤是否彈出的狀態(tài)時(shí),需要注冊一個(gè)`Notification`來響應(yīng)漫蛔;當(dāng)使用`NSURLSession`做網(wǎng)絡(luò)請求時(shí)需要提供一個(gè)`block`來響應(yīng)嗜愈;當(dāng)想要監(jiān)聽一個(gè)屬性值的變化時(shí),需要使用`KVO`來響應(yīng)莽龟;當(dāng)一個(gè)`scrollView`滑動(dòng)時(shí)蠕嫁,需要寫`Delegate`方法來響應(yīng)。
為了響應(yīng)這些事件發(fā)生或者狀態(tài)變化毯盈,系統(tǒng)提供了多種方式: `Delegate`, `KVO`, `Block`, `Notification`, `Target-Action`剃毒。而這也就是問題的根源,寫法不統(tǒng)一最終一定會(huì)導(dǎo)致代碼異常復(fù)雜與混亂搂赋。如果用一種新的方式將上述五種方式合而為一赘阀,會(huì)不會(huì)很大程度提高代碼可讀性?哇咔咔脑奠,那絕對是當(dāng)然的基公。
首先就來看一下如何將五種方式回調(diào)寫法統(tǒng)一。
//T-A
? ? [[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
? ? ? ? // 按鈕被點(diǎn)擊回調(diào)
? ? }];
? ? //Notification
? ? [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
? ? ? ? // 鍵盤彈出回調(diào)
? ? }];
? ? //Block
? ? [[self asyncDataRequest] subscribeNext:^(id x) {
? ? ? ? // 請求成功回調(diào)
? ? } error:^(NSError *error) {
? ? ? ? // 請求錯(cuò)誤回調(diào)
? ? }];
? ? //KVO
? ? [RACObserve(self, name) subscribeNext:^(id x) {
? ? ? ? // 屬性值變化回調(diào)
? ? }];
? ? //Delegate
? ? [[self rac_signalForSelector:@selector(scrollViewDidScroll:) fromProtocol:@protocol(UIScrollViewDelegate)] subscribeNext:^(id x) {
? ? ? ? // scrollView滑動(dòng)回調(diào)
? ? }];
不光如此宋欺,對于任意方法也可以應(yīng)用同樣的回調(diào)方式轰豆。
// Method
? ? [[self rac_signalForSelector:@selector(viewWillAppear:)] subscribeNext:^(id x) {
? ? ? ? // viewWillAppear方法被調(diào)用
? ? }];
每當(dāng)相應(yīng)的事件觸發(fā)或者狀態(tài)改變時(shí)胰伍,`block`中的代碼都會(huì)執(zhí)行。沒有`Target-Action`酸休,沒有`Delegate`骂租,沒有`KVO`,沒有`Notification`斑司,只有`block`渗饮。厲害了有木有?
觀察上面代碼可以發(fā)現(xiàn):代碼高度聚合陡厘,無需跨方法調(diào)用和傳值抽米。這樣優(yōu)點(diǎn)有:
1. 能夠減少方法數(shù)量
2. 減少很多表示狀態(tài)的中間變量
3. 擁有足夠的上下文環(huán)境特占,減少對其他對象的引用
理論上來說糙置,通過上面這種方式一個(gè)類中只要有一個(gè)方法就夠了,雖然現(xiàn)實(shí)中沒有人會(huì)這樣是目。
基本概念
RACEvent
上面提到谤饭,響應(yīng)式編程可以將變化的值通過數(shù)據(jù)流進(jìn)行傳播。為此懊纳,RAC中定義了一個(gè)事件的概念揉抵,即:`RACEvent`。
事件分三種類型:Next類型嗤疯,Completed類型和Error類型冤今。其中Next類型和Error類型事件內(nèi)部可以承載數(shù)據(jù),而Completed類型并不茂缚。
RACSignal
這是RAC中最基本的一個(gè)概念戏罢,中文名叫做“信號”,搞懂了這個(gè)類脚囊,就可以用RAC去玩耍了龟糕。
信號代表的是一個(gè)隨時(shí)間而改變的值流。作為一個(gè)流悔耘,可以將不斷變化的值(或者說數(shù)據(jù))向外進(jìn)行傳播讲岁。想獲取一個(gè)信號中的數(shù)據(jù),需要訂閱它衬以。什么是訂閱呢缓艳?和訂閱博客,訂報(bào)紙看峻,訂牛奶一個(gè)意思郎任。但前提是這個(gè)信號是存在的,所以想要訂閱必先創(chuàng)建备籽。反過來說舶治,創(chuàng)建了一個(gè)信號但是并沒有訂閱它分井,也獲取不到其內(nèi)部的數(shù)據(jù)。(這種情況下RACSignal信號根本就不會(huì)向外發(fā)送數(shù)據(jù)霉猛,下一篇中會(huì)詳細(xì)介紹尺锚,暫時(shí)忽略)。當(dāng)一個(gè)訂閱過程結(jié)束時(shí)惜浅,如有必要去做一些清理工作(當(dāng)然為了回收資源需要將信號銷毀瘫辩,但RAC內(nèi)部會(huì)自動(dòng)處理,使用者無需關(guān)心)坛悉。綜上伐厌,一個(gè)信號完整的使用過程應(yīng)該是創(chuàng)建,訂閱裸影,清理挣轨。
信號被訂閱了之后,可以認(rèn)為在信號源和訂閱者之間建立起了一座橋梁轩猩,通過它信號源源不斷的向訂閱者發(fā)送最新數(shù)據(jù)卷扮,直到橋被銷毀。但是要注意均践,這是一條很窄而且承重很差的橋晤锹,以至于一次只能通過一條數(shù)據(jù)。如果將一條數(shù)據(jù)理解成一個(gè)人彤委,那么通俗的說就是只有一個(gè)人通過了另一個(gè)人才能繼續(xù)過鞭铆,而絕不能同時(shí)兩個(gè)人走上橋。
信號向外傳播數(shù)據(jù)的載體就是事件焦影。其中Next類型事件可以承載任意類型數(shù)據(jù)-即id车遂,甚至可以是nil。但一般不用來承載錯(cuò)誤類型數(shù)據(jù)偷办,因?yàn)橛蠩rror類型事件單獨(dú)做這件事艰额。Completed類型事件僅作為一個(gè)正常完成的標(biāo)志,不承載任何數(shù)據(jù)椒涯。
信號被訂閱了之后柄沮,可以發(fā)送任意多個(gè)Next事件(當(dāng)然可以是0),直到發(fā)送了一個(gè)Completed事件或者一個(gè)Error事件废岂,這兩個(gè)事件都標(biāo)志著結(jié)束祖搓,區(qū)別在于Completed事件表示正常結(jié)束,而Error事件表示因?yàn)槟撤N錯(cuò)誤而結(jié)束湖苞。只要兩者之一被發(fā)送了拯欧,整個(gè)訂閱過程就結(jié)束了。
根據(jù)是否會(huì)發(fā)送一個(gè)表示結(jié)束的事件信號其實(shí)可以分兩類财骨。舉2個(gè)例子镐作,網(wǎng)絡(luò)請求可作為一個(gè)信號藏姐,去訂閱它,調(diào)用API得到結(jié)果后该贾,若成功則將獲得的數(shù)據(jù)通過Next事件發(fā)出羔杨,然后跟一個(gè)Completed事件;反之則將錯(cuò)誤原因通過一個(gè)Error事件發(fā)出杨蛋。此時(shí)這個(gè)API都調(diào)用完成兜材,整個(gè)訂閱過程結(jié)束。按鈕可作為另一個(gè)信號逞力,去訂閱它曙寡,之后每當(dāng)按鈕被點(diǎn)擊時(shí)都會(huì)發(fā)出一個(gè)Next事件,但是在它的整個(gè)生命周期中寇荧,都不會(huì)發(fā)送Completed事件或Error事件举庶。
看一下下面的簡單示意圖:
--1--2--3--4--5--6--|----> // "|" = 正常結(jié)束:發(fā)送一個(gè)Completed事件
--a--b--c--d--e--f--X----> // "X" = 異常結(jié)束:發(fā)送一個(gè)Error事件
--tap--tap----------tap--> // "|" = 一直發(fā)送Next事件
三種類型事件關(guān)系如下圖:
RACSubscriber
信號可以被訂閱,訂閱了信號的對象就是(外部)訂閱者砚亭。注意灯变,信號`RACSignal`本身是沒有發(fā)送事件能力的殴玛。為了實(shí)現(xiàn)向外發(fā)送事件捅膘,內(nèi)部還需要一個(gè)發(fā)送者。為此滚粟,RAC首先定義了一個(gè)`RACSubscriber`協(xié)議寻仗,此協(xié)議中定義了發(fā)送不同事件類型的方法。然后又定義了一個(gè)實(shí)現(xiàn)了此協(xié)議并與其同名的類`RACSubscriber`凡壤。這個(gè)類中文可翻譯成(內(nèi)部)訂閱者署尤。當(dāng)一個(gè)對象訂閱了某個(gè)信號后,就會(huì)產(chǎn)生一次訂閱行為亚侠,對應(yīng)的英文是subscription曹体。此時(shí),RAC內(nèi)部會(huì)創(chuàng)建一個(gè)`RACSubscriber`類的實(shí)例充當(dāng)內(nèi)部訂閱者硝烂,負(fù)責(zé)發(fā)送事件箕别。綜上,雖然有兩個(gè)訂閱者滞谢,但是兩者并不一樣串稀,不要懵逼。官方文檔或者其他博客中提到的訂閱者均是此處所說的內(nèi)部訂閱者狮杨,而本篇文章中提到的訂閱者均是此處所說的外部訂閱者母截。
舉個(gè)??:
當(dāng)你訂閱了每天一次的牛奶時(shí),這個(gè)訂閱行為叫做:subscription橄教;此時(shí)你是一個(gè)(外部)訂閱者清寇;而每天負(fù)責(zé)向你送牛奶的送奶工喘漏,就是一個(gè)(內(nèi)部)訂閱者:subscriber。
RACDisposable
前面提到华烟,當(dāng)一個(gè)信號的訂閱過程結(jié)束時(shí)陷遮,如有必要去做一些清理工作,為此垦江,RAC定義了一個(gè)`RACDisposable`類帽馋,中文可以叫做“清理者”。
綜上比吭,RAC最核心最基本的概念就是這些绽族,四個(gè)名詞類:RACEvent(事件),RACSignal(信號)衩藤,RACSubscriber(發(fā)送者)吧慢,RACDisposable(清理者);三個(gè)動(dòng)詞:創(chuàng)建(create)赏表,訂閱(subscribe)检诗,清理(dispose)。
簡單使用
創(chuàng)建
創(chuàng)建一個(gè)信號非常簡單瓢剿,`RACSignal`定義了一個(gè)類方法如下:
+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe;
調(diào)用這個(gè)方法就會(huì)成功創(chuàng)建一個(gè)signal對象逢慌,并且其內(nèi)部會(huì)自動(dòng)創(chuàng)建一個(gè)實(shí)現(xiàn)`RACSubscriber`協(xié)議的對象`subscriber`,負(fù)責(zé)對外發(fā)送事件间狂。
不難猜出攻泼,`RACSubscriber`協(xié)議中定義的發(fā)送三種不同事件類型的方法分別如下:
- (void)sendNext:(id)value;
- (void)sendError:(NSError *)error;
- (void)sendCompleted;
舉個(gè)??:
RACSignal *sourceSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
? ? ? ? [subscriber sendNext:@"????"];
? ? ? ? [subscriber sendCompleted];
? ? ? ? return [RACDisposable disposableWithBlock:^{
? ? ? ? }];
? ? }];
訂閱
信號有了,那如何訂閱呢鉴象?仍然非常簡單忙菠,`RACSignal`中定義了一些方法如下:
僅訂閱next類型事件:
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock;
僅訂閱next和error類型事件:
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock;
同時(shí)訂閱三種類型事件:
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
以及:
- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock;
- (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock;
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock;
- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
根據(jù)實(shí)際想訂閱內(nèi)容的不同可以有選擇性的使用不同的方法,通常來說上面三個(gè)比較常用纺弊。
舉個(gè)??:
[sourceSignal subscribeNext:^(id x) {
? ? ? ? NSLog(@"接收到next類型事件:%@",x);
? ? } error:^(NSError *error) {
? ? ? ? NSLog(@"接收到error類型事件:%@",error);
? ? } completed:^{
? ? ? ? NSLog(@"接收到completed類型事件牛欢,不包含任何數(shù)據(jù)");
? ? }];
此時(shí),`sourceSignal`發(fā)送任何數(shù)據(jù)都能被接收到淆游。
清理
注意上面`createSignal`方法中的`block`返回的是一個(gè)`RACDisposable`類型對象傍睹,`subscribe`方法返回的也是一個(gè)`RACDisposable`類型對象。下面就來說說這個(gè)類怎么用稽犁。
`RACDisposable`類有一個(gè)類方法:
+ (instancetype)disposableWithBlock:(void (^)(void))block;
就像上面展示過的焰望,使用它就創(chuàng)建了一個(gè)`disposable`對象。`block`中可以寫一些資源回收和垃圾清理的代碼已亥。比如如果是一個(gè)網(wǎng)絡(luò)請求就取消這個(gè)請求熊赖,如果是打開一個(gè)文件就關(guān)閉這個(gè)文件等。
當(dāng)信號的訂閱過程結(jié)束時(shí)虑椎,`block`中的代碼會(huì)自動(dòng)執(zhí)行震鹉。
當(dāng)然俱笛,有時(shí)不需要任何清理,那么`block`中就是空的传趾。這種情況也可以不返回一個(gè)`RACDisposable`類型對象而是直接返回一個(gè)`nil`迎膜。
舉個(gè)??:
RACSignal *sourceSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
? ? ? ? [subscriber sendNext:@"????"];
? ? ? ? [subscriber sendCompleted];
? ? ? ? return nil;
? ? }];
那信號的訂閱過程如何結(jié)束呢?這里分兩種情況浆兰,分別是訂閱正常完成而結(jié)束和訂閱中途被取消而結(jié)束磕仅。
訂閱正常完成是指當(dāng)信號源發(fā)送完所有的next事件后,發(fā)送一個(gè)completed或error事件簸呈。訂閱中途被取消是指信號源還沒有發(fā)送結(jié)束事件時(shí)訂閱者就不再繼續(xù)訂閱了榕订。
RAC中并沒有明確定義一個(gè)取消訂閱的方法,但是`RACDisposable`類中定義了如下一個(gè)方法:
- (void)dispose;
調(diào)用這個(gè)方法就表示不再訂閱(也就是取消訂閱)可以直接去清理資源了蜕便。
舉個(gè)??:
RACDisposable *disposable = [sourceSignal subscribeNext:^(id x) {
NSLog(@"接收到next類型事件:%@",x);
}];
[disposable dispose];
通常來說應(yīng)用這種方式的情況非常少劫恒。
綜上,訂閱過程如下圖:
下面來看一個(gè)完整的流程:
發(fā)送next類型事件以completed結(jié)束時(shí):
// 1 信號未被創(chuàng)建 RACSignal *sourceSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
? ? ? ? // 3 信號被激活轿腺,開始發(fā)送事件
? ? ? ? [subscriber sendNext:@"????"];
? ? ? ? [subscriber sendCompleted];
? ? ? ? return [RACDisposable disposableWithBlock:^{
? ? ? ? ? ? // 6 訂閱流程結(jié)束两嘴,可清理資源
? ? ? ? }];
? ? }];
? ? // 2 信號已被創(chuàng)建,未被訂閱(未激活)
? ? [sourceSignal subscribeNext:^(id x) {
? ? ? ? // 4 信號已被訂閱族壳,可接收next類型事件
? ? ? ? NSLog(@"接收到next類型事件:%@",x);
? ? } error:^(NSError *error) {
? ? ? ? // 發(fā)送next與completed類型事件時(shí)憔辫,此處不會(huì)走到
? ? ? ? NSLog(@"接收到error類型事件:%@",error);
? ? } completed:^{
? ? ? ? // 5 信號已被訂閱,可接收completed類型事件
? ? ? ? NSLog(@"接收到completed類型事件");
? ? }];
結(jié)果是:
接收到next類型事件:????
接收到completed類型事件?
未發(fā)送next類型事件以error結(jié)束時(shí):
// 1 信號未被創(chuàng)建 RACSignal *sourceSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
? ? ? ? // 3 信號被激活决侈,開始發(fā)送事件
? ? ? ? [subscriber sendError:[NSError errorWithDomain:@"www.reactivecocoademo.com" code:202 userInfo:nil]];
? ? ? ? return [RACDisposable disposableWithBlock:^{
? ? ? ? ? ? // 5 訂閱流程結(jié)束螺垢,可清理資源
? ? ? ? }];
? ? }];
? ? // 2 信號已被創(chuàng)建喧务,未被訂閱(未激活)
? ? [sourceSignal subscribeNext:^(id x) {
? ? ? ? // 僅發(fā)送error類型事件時(shí)赖歌,此處不會(huì)走到
? ? ? ? NSLog(@"接收到next類型事件:%@",x);
? ? } error:^(NSError *error) {
? ? ? ? // 4 信號已被訂閱,可接收error類型事件
? ? ? ? NSLog(@"接收到error類型事件:%@",error);
? ? } completed:^{
? ? ? ? // 發(fā)送error類型事件時(shí)功茴,此處不會(huì)走到
? ? ? ? NSLog(@"接收到completed類型事件");
? ? }];
結(jié)果是:
接收到error類型事件:Error Domain=www.reactivecocoademo.com Code=202 "(null)"
其中注釋前面的數(shù)字表示代碼執(zhí)行的先后順序庐冯。
除此之外,在信號發(fā)送的數(shù)據(jù)被訂閱者接收到之前還可以攔截到而添加一些附加操作坎穿,有點(diǎn)面向切片編程的意思展父。
接收next類型事件以及completed事件之前做些事情:
// 1 信號未被創(chuàng)建 RACSignal *sourceSignal = [[[[RACSignal createSignal:^RACDisposable *(id subscriber) {
? ? ? ? // 3 信號被激活,開始發(fā)送事件
? ? ? ? [subscriber sendNext:@"????"];
? ? ? ? [subscriber sendCompleted];
? ? ? ? return [RACDisposable disposableWithBlock:^{
? ? ? ? ? ? // 8 訂閱流程結(jié)束玲昧,可清理資源
? ? ? ? }];
? ? }] doNext:^(id x) {
? ? ? ? // 4 信號被激活栖茉,next類型事件已發(fā)送,before接收到
? ? ? ? NSLog(@"before 接收到next類型事件:%@",x);
? ? }] doError:^(NSError *error) {
? ? ? ? // 發(fā)送next與completed類型事件時(shí)孵延,此處不會(huì)走到
? ? }] doCompleted:^{
? ? ? ? // 6 信號被激活吕漂,before發(fā)送completed類型事件
? ? }];
? ? // 2 信號已被創(chuàng)建,未被訂閱(未激活)
? ? [sourceSignal subscribeNext:^(id x) {
? ? ? ? // 5 信號已被訂閱尘应,可接收next類型事件
? ? ? ? NSLog(@"接收到next類型事件:%@",x);
? ? } error:^(NSError *error) {
? ? ? ? // 發(fā)送next與completed類型事件時(shí)惶凝,此處不會(huì)走到
? ? } completed:^{
? ? ? ? // 7 信號已被訂閱吼虎,可接收completed類型事件
? ? ? ? NSLog(@"接收到completed類型事件");
? ? }];
結(jié)果是:
before 接收到next類型事件:????
接收到next類型事件:????
接收到completed類型事件
接收error類型事件之前做些事情:
// 1 信號未被創(chuàng)建 RACSignal *sourceSignal = [[[[RACSignal createSignal:^RACDisposable *(id subscriber) {
? ? ? ? // 3 信號被激活,開始發(fā)送事件
? ? ? ? [subscriber sendError:[NSError errorWithDomain:@"www.reactivecocoademo.com" code:202 userInfo:nil]];
? ? ? ? return [RACDisposable disposableWithBlock:^{
? ? ? ? ? ? // 6 訂閱流程結(jié)束苍鲜,可清理資源
? ? ? ? }];
? ? }] doNext:^(id x) {
? ? ? ? // 僅發(fā)送error類型事件時(shí)思灰,此處不會(huì)走到
? ? }] doError:^(NSError *error) {
? ? ? ? // 4 信號被激活,error類型事件已發(fā)送混滔,before接收到
? ? ? ? NSLog(@"before 接收到error類型事件:%@",error);
? ? }] doCompleted:^{
? ? ? ? // 發(fā)送error類型事件時(shí)洒疚,此處不會(huì)走到
? ? }];
? ? // 2 信號已被創(chuàng)建,未被訂閱(未激活)
? ? [sourceSignal subscribeNext:^(id x) {
? ? ? ? // 僅發(fā)送error類型事件時(shí)坯屿,此處不會(huì)走到
? ? } error:^(NSError *error) {
? ? ? ? // 5 信號已被訂閱拳亿,可接收error類型事件
? ? ? ? NSLog(@"接收到error類型事件:%@",error);
? ? } completed:^{
? ? ? ? // 發(fā)送error類型事件時(shí),此處不會(huì)走到
? ? }];
結(jié)果是:
before 接收到error類型事件:Error Domain=www.reactivecocoademo.com Code=202 "(null)"
接收到error類型事件:Error Domain=www.reactivecocoademo.com Code=202 "(null)"
See? 就是這么簡單愿伴。
最后肺魁,再來看一眼文章開頭出現(xiàn)過的圖片,有沒有感覺豁然開朗隔节?
參考鏈接
官方文檔:
宏觀介紹:
入門經(jīng)典:
ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2
ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2
國人寫的高質(zhì)量文章:
ReactiveCocoa v2.5 源碼解析之架構(gòu)總覽
我之前寫的: