ReactiveObjC基本概念與簡單使用

前幾個(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)過的圖片,有沒有感覺豁然開朗隔节?



參考鏈接

官方文檔:

ReactiveObjCIntroduction

ReactiveObjCDocumentation

宏觀介紹:

Reactive?Cocoa

入門經(jīng)典:

ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2

ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2

國人寫的高質(zhì)量文章:

iOS開發(fā)下的函數(shù)響應(yīng)式編程

ReactiveCocoa v2.5 源碼解析之架構(gòu)總覽

我之前寫的:

ReactiveCocoaDemo

RxSwift基本概念與使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹅经,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子怎诫,更是在濱河造成了極大的恐慌瘾晃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幻妓,死亡現(xiàn)場離奇詭異蹦误,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)肉津,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門强胰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妹沙,你說我怎么就攤上這事偶洋。” “怎么了距糖?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵玄窝,是天一觀的道長。 經(jīng)常有香客問我悍引,道長恩脂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任趣斤,我火速辦了婚禮俩块,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己典阵,他們只是感情好奋渔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著壮啊,像睡著了一般嫉鲸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上歹啼,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天玄渗,我揣著相機(jī)與錄音,去河邊找鬼狸眼。 笑死藤树,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拓萌。 我是一名探鬼主播岁钓,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼微王!你這毒婦竟也來了屡限?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤炕倘,失蹤者是張志新(化名)和其女友劉穎钧大,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罩旋,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啊央,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涨醋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓜饥。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖东帅,靈堂內(nèi)的尸體忽然破棺而出压固,到底是詐尸還是另有隱情,我是刑警寧澤靠闭,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站坎炼,受9級特大地震影響愧膀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谣光,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一檩淋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦蟀悦、人聲如沸媚朦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽询张。三九已至,卻和暖如春浙炼,著一層夾襖步出監(jiān)牢的瞬間份氧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工弯屈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜗帜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓资厉,卻偏偏與公主長得像厅缺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子宴偿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容