ReactiveCocoa學習筆記(四):「RAC微博」基礎使用手冊

本文簡單介紹了ReactiveCocoa的基礎用法,希望讀完能對這個框架的使用有一個大概的了解旧噪。

前面幾篇文章吨娜,我們研究了ReactiveCocoa(以下簡稱RAC)的起源和思想。網絡上介紹RAC的使用的好文甚多淘钟,在文末的Reference中標出了一些以供參考宦赠。RAC這個開源庫本身也是十分良心,代碼注釋十分齊全米母,因此這一篇文章不做太多的擴展勾扭,僅談一談RAC的基礎使用方法。

RACSignal

上文中說到铁瞒,函數(shù)響應式編程中妙色,將各種通信機制所需要解決的「輸入」與「輸出」的異步關系抽象成了事件/時間驅動的值流,并通過monad使其支持了函數(shù)式編程的特性慧耍。而在RAC中身辨,這個東西就是RACStream,開發(fā)過程中我們并不是直接使用它芍碧,而是其子類——RACSignalRACSequence栅表。這一節(jié),講講RACSignal师枣。

Signal怪瓶,顧名思義,代表一個信號,可以源源不斷地給你傳遞信息洗贰。這樣就好理解RACSignal代表著「隨時間變化的值流」找岖,這里的值,就包含了將來即將到來的「輸入」敛滋。打個比方许布,一個微博博主便是一個「Signal」,只要沒被封號绎晃,你就會知道將來他會一直發(fā)出消息蜜唾。如果關注了這個博主,一旦他開始發(fā)消息庶艾,新消息會被自動推送到你的設備袁余,因此說RACSignal是一個Push-Driven的值流。

那么咱揍,RACSignal博主會發(fā)出什么消息呢颖榜?一個RACSignal傳遞的值分為三類:

  1. Next∶喝梗「Next」代表著一個新的值掩完,一條新的微博。只要這個博主是活躍的硼砰,他就會源源不斷地發(fā)微博且蓬。
  2. Error√夂玻「Error」則代表著這個Signal出了什么問題缅疟,發(fā)出了一個代表「錯誤」的信號。發(fā)送出「Error」也就意味著這個Signal的消息到此為止了遍愿。比如這位博主被封號了存淫,他就會給你發(fā)一條微博,上面寫著「404Error」沼填,你就知道他再也不會發(fā)微博了……
  3. Completed桅咆。代表一個Signal完成了自己的全部信息發(fā)送。比如某天這個博主想退出微博了坞笙,于是發(fā)出最后一條微博——「ByeBye粉絲們」岩饼。這就是「Completed」。

一個Signal的信息流薛夜,都是由若干個「Next」籍茧,加上一個代表終結的「Error」或「Completed」組成的。

這些值都是從哪里來的呢梯澜?一個Signal所發(fā)出的信息主要來源有兩種:

  1. 手動創(chuàng)建一個信號時定義它發(fā)出的信息寞冯。這就好像是一位原創(chuàng)博主,每條微博都是他自己寫的:
// 代碼1
RACSignal *blogSignalA =
[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"blog1"];
    [subscriber sendNext:@"blog2"];
    [subscriber sendNext:@"blog3"];
    [subscriber sendCompleted];
    return nil;
}];
  1. 由其他通信機制生成一個信號時,在其他通信機制產生輸入時發(fā)出消息吮龄。比如某些大V博主的微博就是專門從雜志俭茧、知乎等其他信息載體上將信息搬運過來。RAC提供了很多有力的工具漓帚,讓我們從傳統(tǒng)的Cocoa通信機制中制造出一個信號來:
// 代碼2
// signal from KVO
RACSignal *blogSignalA = RACObserve(someNewspaper, news);

// signal from UIControl events
RACSignal *blogSignalB = [someButton rac_signalForControlEvents:UIControlEventTouchUpInside];

// signal from selectors
RACSignal *blogSignalB = [self rac_signalForSelector:@selector(viewWillAppear:)];

好了母债,現(xiàn)在有一個博主能夠發(fā)出很多消息。但如果沒有人關注他尝抖,這些信息也不會有多大的作用毡们。對于一個Signal也是一樣,創(chuàng)建不是目的昧辽,獲取它發(fā)出的信息才是我們所需要的衙熔。在RAC中,這種行為叫「訂閱」(Subscribe)奴迅。例如,我們想在收到消息時挺据,把消息打印出來取具,或者做一些其他的事情:

// 代碼3
RACSignal *blogSignalA =
[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"blog1"];
    [subscriber sendNext:@"blog2"];
    [subscriber sendNext:@"blog3"];
    [subscriber sendCompleted];
    return nil;
}];

//subscribe to blogSignalA
[blogSignalA subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@",x);
    //do something else
} error:^(NSError * _Nullable error) {
    NSLog(@"%@",error);
} completed:^{
    NSLog(@"Complete!");
}];

現(xiàn)在,我們就關注了blogSignalA這位博主扁耐,他發(fā)出的blog1暇检,blog2等等微博都會推送到我們,由我們進行處理婉称。RAC對于信號的「訂閱者」是有要求的块仆,它必須實現(xiàn)了RACSubscriber協(xié)議:

// 代碼4
@protocol RACSubscriber <NSObject>
@required
- (void)sendNext:(id)value;
- (void)sendError:(NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
@end

這也很好理解,因為「訂閱者」至少得知道自己需要用這些訂閱的值來做什么王暗。上面的代碼3中的subscribeNext:error:completed:其實就是幫我們創(chuàng)建了一個內部的「訂閱者」悔据,這些在后續(xù)如果深入探究源碼的時候會詳細說明。

此外俗壹,Signal支持各種函數(shù)式的操作科汗,例如mapreduce绷雏,filter等等头滔。這可以讓我們方便地對原始信號傳輸出的信息進行一步步加工,最終得到我們所需要的值涎显,這就是「函數(shù)性」賦予的利器:

// 代碼5
RACSignal *blogSignalA =
[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"Sunday"];
    [subscriber sendNext:@"Monday"];
    [subscriber sendNext:@"Tuesday"];
    [subscriber sendNext:@"Wednesday"];
    [subscriber sendNext:@"Thursday"];
    [subscriber sendNext:@"Friday"];
    [subscriber sendNext:@"Saturday"];
    [subscriber sendCompleted];
    return nil;
}];

RACSignal *blogSignalB =
[blogSignalA map:^id _Nullable(NSString *  _Nullable value) {
    if ([value isEqualToString:@"Sunday"] || [value isEqualToString:@"Saturday"]) {
        return @"Weekend";
    }else {
        return @"Workday";
    }
}];

RACSignal *blogSignalC =
[blogSignalB filter:^BOOL(NSString *  _Nullable value) {
    return [value isEqualToString:@"Weekend"];
}];

[blogSignalC subscribeNext:^(id  _Nullable x) {
    NSLog(@"Wow Weekend! Time to Relax!");
}];

這里博主A是一個報時的微博坤检,而博主B是一個翻譯的微博,它將A發(fā)出的微博進行加工期吓,然后發(fā)出「Weekend」和「Workday」兩種微博早歇。博主C負責過濾B發(fā)出的微博,屏蔽了所有工作日的消息(Nice)。最后我們關注博主C缺前,就能在收到消息推送的時候知道該出去玩啦蛀醉!當然,有了Monad的保證衅码,我們也可以采用鏈式語法這么寫:

// 代碼6
RACSignal *blogSignalD =
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"Sunday"];
    [subscriber sendNext:@"Monday"];
    [subscriber sendNext:@"Tuesday"];
    [subscriber sendNext:@"Wednesday"];
    [subscriber sendNext:@"Thursday"];
    [subscriber sendNext:@"Friday"];
    [subscriber sendNext:@"Saturday"];
    [subscriber sendCompleted];
    return nil;
}] map:^id _Nullable(NSString *  _Nullable value) {
    if ([value isEqualToString:@"Sunday"] || [value isEqualToString:@"Saturday"]) {
        return @"Weekend";
    }else {
        return @"Workday";
    }
}] filter:^BOOL(id  _Nullable value) {
    return [value isEqualToString:@"Weekend"];
}];

[blogSignalD subscribeNext:^(id  _Nullable x) {
    NSLog(@"Wow Weekend! Time to Relax!");
}];

總結一下拯刁,RACSignal的基本操作主要是三個:創(chuàng)建(手動創(chuàng)建+由其他通信機制生成),訂閱逝段,以及轉換垛玻。

RACSubject

上面討論的RACSignal,其實細究起其行為奶躯,是和微博不太一樣的帚桩。從代碼1代碼3中可以看出,RACSignal所能發(fā)出的信號是定義好的嘹黔,即創(chuàng)建該Signal的時候就已經確定了账嚎。這更像是一個「微博機器人」,每當有一個新的粉絲來訂閱它儡蔓,它便按照一個「創(chuàng)建腳本程序」從頭開始生成若干微博進行推送郭蕉。這種行為是依賴于「訂閱」的,只有當「訂閱」發(fā)生的時候喂江,才會對新的訂閱者發(fā)送內容召锈。我們稱之為「冷信號(Cold Signal)」。

這種「Cold Signal」會帶來一個問題获询。譬如說涨岁,這個機器人在它的「創(chuàng)建腳本程序」中進行了其他的操作:

// 代碼7
RACSignal *blogSignalA =
[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    NSLog(@"Ready To Send!");
    [subscriber sendNext:@"blog1"];
    [subscriber sendNext:@"blog2"];
    [subscriber sendNext:@"blog3"];
    NSLog(@"Sending Comleted!");
    [subscriber sendCompleted];
    return nil;
}];

那么,每當有一個新的訂閱者吉嚣,這兩個NSLog的操作就會重復執(zhí)行一遍梢薪。我們把這種操作稱為「副作用(Side-Effect)」。想象一下如果把上面簡單的NSLog換成非常復雜的操作尝哆,比如網絡請求沮尿,那么這樣的「副作用」就非常明顯了。因為我們可能只是想進行一次網絡請求较解。RAC中主要使用RACSubject來解決這個問題畜疾。

RACSubjectRACSignal的子類。是不同于只會根據(jù)腳本發(fā)送固定信號的RACSignal印衔,RACSubject能夠由我們程序控制啡捶,在任何時候主動發(fā)送新的值。這有點類似于不可變數(shù)組和可變數(shù)組的概念奸焙∠故睿可以想象這樣一種情景彤敛,我們要在原有的舊代碼里利用RAC完成一些功能,那么可以利用RACSubject了赌,在老代碼中間手動控制其發(fā)送出信號墨榄。因此,官方稱RACSubject為「most helpful in bridging the non-RAC world to RAC」勿她。

RACSubject是一個「熱信號」袄秩,它在內部維護了一個「訂閱者」的統(tǒng)計數(shù)組。每當產生新的訂閱行為的時候逢并,它只是簡單地將這個「訂閱者」添加進自己維護的數(shù)組中之剧。等到發(fā)出信號的時候,會遍歷該數(shù)組砍聊,向其中所有的「訂閱者」發(fā)送該信號背稼。也就是說,它不會管有沒有訂閱行為玻蝌,而只是自己發(fā)自己的信號蟹肘。而訂閱之后,也只能收到它以后發(fā)出的信號俯树。嗯帘腹,這樣的行為才是一個活生生的大V博主,而不是冰冷的腳本嘛聘萨!

同時竹椒,RACSubject還是一個「訂閱者」童太,它實現(xiàn)了RACSubscriber協(xié)議米辐,也就是說,它可以訂閱一個「RACSignal」书释。當接受到「RACSignal」發(fā)送的信號的時候翘贮,它會遍歷其內部的「訂閱者」數(shù)組,將自己接收到的信號轉發(fā)給每一個「訂閱者」爆惧。也就是說狸页,RACSubject充當了一個中間轉發(fā)者的角色。這樣既保證了對原始信號只訂閱一次扯再,從而可以消除副作用的影響芍耘,又保證了外界多個訂閱者的正常行為。

<div align=center>


image

</div>

在RAC中熄阻,這種關系是由RACMulticastConnection類以及multicastconnect操作實現(xiàn)的:

// 代碼8
RACSignal *sideEffectSignal =
[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    NSLog(@"This is side-effect~~~");
    [subscriber sendNext:@"1"];
    [subscriber sendNext:@"2"];
    [subscriber sendNext:@"3"];
    [subscriber sendCompleted];
    return [RACDisposable disposableWithBlock:^{

    }];
}];

RACSubject *subject = [RACSubject subject];
RACMulticastConnection *multiConnect = [sideEffectSignal multicast:subject];

//subscribe A
[multiConnect.signal subscribeNext:^(id  _Nullable x) {
    NSLog(@"A receive next: %@",x);
} error:^(NSError * _Nullable error) {
    NSLog(@"A receive error: %@",error);
} completed:^{
    NSLog(@"A receive completed");
}];

//subscribe B
[multiConnect.signal subscribeNext:^(id  _Nullable x) {
    NSLog(@"B receive next: %@",x);
} error:^(NSError * _Nullable error) {
    NSLog(@"B receive error: %@",error);
} completed:^{
    NSLog(@"B receive completed");
}];

[multiConnect connect];

/*
Log result:
This is side-effect~~~
A receive next: 1
B receive next: 1
A receive next: 2
B receive next: 2
A receive next: 3
B receive next: 3
A receive completed
B receive completed
*/

RACMulticastConnection類封裝了原始信號以及充當「中間人」的RACSubject對象斋竞,調用[multiConnect connect]會將「中間人」與原始信號連接起來(使用封裝的RACSubject訂閱原始RACSignal)。注意這里調用[multiConnect connect]的時機秃殉,如果將其提前到subscribe Asubscribe B之前坝初,那么A和B將完全接收不到原始信號發(fā)出的消息浸剩,這還是因為RACSubject是一個「熱信號」的原因。如果確實需要先執(zhí)行connect操作鳄袍,那么在創(chuàng)建RACMulticastConnection時可以使用RACSubject的子類绢要,如RACReplaySubject等來實現(xiàn)具體的需求。

RACSequence

上面說的RACSignal是一個Push-driven的值流拗小,而RACSequence則是一個Pull-driven的值流重罪,它們的關系就好像是后臺推送和客戶端主動拉取兩種不同行為。

RACSequence主要用于簡化集合的操作十籍,以及對Cocoa中的基礎集合類型提供函數(shù)性的工具蛆封。譬如說,

// 代碼9
NSArray *names = @[@"Peter",@"John",@"Steve",@"Jim"];
[[names.rac_sequence.signal filter:^BOOL(NSString * _Nullable value) {
    return value.length > 4;
}] subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@",x);
}];

一般情況下勾栗,RACSequence會采用惰性計算惨篱,即要獲取其中某個元素的時候再去對該元素進行計算。具體思想可以參考臧老師的聊一聊iOS開發(fā)中的惰性計算

RACCommand

除了上面討論的幾種信號围俘,RAC還為我們提供了很多實用而充滿技巧的工具類砸讳。RACCommand就是其中一個。顧名思義界牡,它是對一個「操作命令」的封裝:這個操作命令會產生一系列的結果輸出簿寂,而RACCommand提供了豐富的接口來控制該操作命令的執(zhí)行、取消宿亡,操作的狀態(tài)流等等常遂。

想象一下「人民日報」微博的小編,他掌握著一份程序挽荠,能夠從人民日報官網拉取當天最新的新聞克胳,將這些新聞生成一系列的微博發(fā)出。有了這個程序圈匆,他每天的工作就很輕松了:執(zhí)行一下這個程序就可以了(小編不要打我…)

RACCommand提供了兩個初始化方法:

- (instancetype)initWithSignalBlock:(RACSignal<ValueType> * (^)(InputType _Nullable input))signalBlock;
- (instancetype)initWithEnabled:(nullable RACSignal<NSNumber *> *)enabledSignal signalBlock:(RACSignal<ValueType> * (^)(InputType _Nullable input))signalBlock;

其中signalBlock就是上面說的「會產生一系列結果輸出的操作命令」漠另,而enabledSignal的值則控制了是否能執(zhí)行該操作。RACCommand提供了excute接口來執(zhí)行操作命令:

- (RACSignal<ValueType> *)execute:(nullable InputType)input;

成功執(zhí)行操作后跃赚,產生的結果由executionSignals返回笆搓,每次成功執(zhí)行,都會返回一個RACSignal纬傲,所以該屬性是一個「高階信號」满败,即「Signal of signal」;倘若執(zhí)行失敗叹括,則會由errors返回:

@property (nonatomic, strong, readonly) RACSignal<RACSignal<ValueType> *> *executionSignals;
@property (nonatomic, strong, readonly) RACSignal<NSError *> *errors;

RACCommand還提供了監(jiān)控當前操作狀態(tài)的屬性:

@property (nonatomic, strong, readonly) RACSignal<NSNumber *> *executing;
@property (nonatomic, strong, readonly) RACSignal<NSNumber *> *enabled;

舉個栗子:

// 代碼10
RACSignal *enalbeSignal =
[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"Sunday"];
    [subscriber sendNext:@"Monday"];
    [subscriber sendNext:@"Tuesday"];
    [subscriber sendNext:@"Wednesday"];
    [subscriber sendNext:@"Thursday"];
    [subscriber sendNext:@"Friday"];
    [subscriber sendNext:@"Saturday"];
    [subscriber sendCompleted];
    return nil;
}] map:^id _Nullable(NSString *  _Nullable value) {
    if ([value isEqualToString:@"Sunday"] || [value isEqualToString:@"Saturday"]) {
        return @(NO));
    }else {
        return @(YES));
    }
}];

self.command =
[[RACCommand alloc] initWithEnabled:enalbeSignal
                        signalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
                            return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
                                [self fetchNewsWithCallback:^(NSError *error, id result) {
                                    if (result) {
                                        [subscriber sendNext:result];
                                        [subscriber sendCompleted];
                                    }
                                    else {
                                        [subscriber sendError:error];
                                    }
                                }];
                                return [RACDisposable disposableWithBlock:^{
                                    NSLog(@"send command disposable");
                                }];
                            }] ;
                        }];

// The final signal for blog
RACSignal *blogSignal = [self.command.executionSignals flatten];

上面的例子創(chuàng)建了一個RACCommand算墨,用來幫助小編同學在工作日從服務器拉取新聞,然后發(fā)送微博领猾。需要注意的是米同,RACCommand在執(zhí)行操作后骇扇,已將該操作生成的RACSignalRACReplaySubject進行了multicast,所以不用擔心內部操作所包含的副作用問題面粮。

可以看出少孝,RACCommandUIButton的作用是很相似的:都是用于執(zhí)行某個操作。事實上熬苍,RAC為UIButton提供了十分方便的category稍走,能生成一個RACCommand并綁定到button上,使得該button的點擊事件柴底、enable狀態(tài)等等都可以通過這個RACCommand完成:

self.senderButton.rac_command = self.command;

總結

這里我們用微博的例子來簡單介紹了一下RAC中一些基礎組件的用法婿脸。RAC的功能遠遠不止這幾個基礎組件,甚至遠遠不止是組件所提供的api柄驻。它更代表一種編程風格狐树,一種代碼思想。

當然鸿脓,從這篇文章抑钟,以及自己的實踐也可以看出,RAC還是存在一些缺點:

  • RAC對代碼的侵入性很強野哭,如果選擇了使用它在塔,項目代碼將和RAC庫產生很強的耦合。
  • RAC不利于團隊協(xié)作拨黔。如果有些團隊成員不熟悉蛔溃,那么將很難調試和修改其他成員用RAC編寫的代碼。
  • 調試不友好篱蝇。由于RAC內部操作相當復雜贺待,即使一行簡單的代碼,調試時的堆棧也完全是RAC內部的堆棧信息态兴。也因此狠持,RAC官方更推薦使用namelog進行調試疟位。
  • 學習曲線還是比較陡峭的瞻润,需要理解函數(shù)響應式編程的思想,以及學習RAC的基本知識甜刻。調bug的時候甚至還需要充分了解RAC內部原理绍撞。

因此,是否使用RAC到實際的大型項目中得院,這是個見仁見智的話題傻铣。但是一些小項目或是自己學習、研究祥绞、使用非洲,還是十分有價值的鸭限。


Reference

霜神解讀RAC源碼系列
美團點評技術博客的RAC系列
Draveness解讀的RAC源碼系列
ReactiveCocoa and MVVM, an Introduction

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市两踏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖番甩,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猜憎,死亡現(xiàn)場離奇詭異,居然都是意外死亡帕识,警方通過查閱死者的電腦和手機泛粹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肮疗,“玉大人晶姊,你說我怎么就攤上這事∥被酰” “怎么了帽借?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長超歌。 經常有香客問我砍艾,道長,這世上最難降的妖魔是什么巍举? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任脆荷,我火速辦了婚禮,結果婚禮上懊悯,老公的妹妹穿的比我還像新娘蜓谋。我一直安慰自己,他們只是感情好炭分,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布桃焕。 她就那樣靜靜地躺著,像睡著了一般捧毛。 火紅的嫁衣襯著肌膚如雪观堂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天呀忧,我揣著相機與錄音师痕,去河邊找鬼。 笑死而账,一個胖子當著我的面吹牛胰坟,可吹牛的內容都是我干的。 我是一名探鬼主播泞辐,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼笔横,長吁一口氣:“原來是場噩夢啊……” “哼竞滓!你這毒婦竟也來了?” 一聲冷哼從身側響起吹缔,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虽界,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后涛菠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體莉御,經...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年俗冻,在試婚紗的時候發(fā)現(xiàn)自己被綠了礁叔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡迄薄,死狀恐怖琅关,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情讥蔽,我是刑警寧澤涣易,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站冶伞,受9級特大地震影響新症,放射性物質發(fā)生泄漏。R本人自食惡果不足惜响禽,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一徒爹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芋类,春花似錦隆嗅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贮竟,卻和暖如春丽焊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坝锰。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工粹懒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留重付,地道東北人顷级。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像确垫,于是被迫代替她去往敵國和親弓颈。 傳聞我的和親對象是個殘疾皇子帽芽,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容

  • RAC使用測試Demo下載:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees閱讀 6,372評論 3 10
  • 1.ReactiveCocoa簡介 ReactiveCocoa(簡稱為RAC),是由Github開源的一個應用于i...
    清蘂翅膀的技術閱讀 1,988評論 0 1
  • 1.ReactiveCocoa簡介 ReactiveCocoa(簡稱為RAC),是由Github開源的一個應用于i...
    愛睡覺的魚閱讀 1,145評論 0 1
  • 標簽: iOS RAC 概述 ReactiveCocoa是一個函數(shù)響應式編程框架,它能讓我們脫離Cocoa AP...
    GodyZ閱讀 7,534評論 16 97
  • 今天仍然沒有走還在中國的港口翔冀,鎮(zhèn)江長江里导街,現(xiàn)在離家不是很遠,但不能回去纤子。 第三天在機艙搬瑰,噪音確實有點大,主要油氣也...
    大皖安利閱讀 267評論 0 0