iOS學習筆記39 ReactiveCocoa入門

執著丶執念發表於2018-06-02

###一、響應式程式設計正規化FRP FRP,全稱為Functional Reactive Programming,是一種響應變化的程式設計正規化,最近幾年比較火,大概的理解就像這樣:

iOS學習筆記39 ReactiveCocoa入門
當a的值或者b的值發生變化時,c的值會自動響應a的值或b的值變化的訊號,自動更正自己的值,類似這種程式設計思想就稱為FRP。

FRP提供了一種訊號機制來實現這樣的效果,通過訊號來記錄值的變化。訊號可以被疊加、分割或合併。通過對訊號的組合,就不需要去監聽某個值或事件。

###二、ReactiveCocoa介紹 ReactiveCocoa是github開源的一個第三方框架,是在iOS平臺上對FRP的實現。 FRP的核心是訊號,訊號在ReactiveCocoa(以下簡稱RAC)中是通過RACSignal來表示的,訊號是資料流,可以被繫結和傳遞。

######下面簡單介紹下RAC的幾個概念:

  1. Signal:訊號管,可以想象成水龍頭
  1. Value:訊號值,可以想象成玻璃球 可以把訊號管(Signal)想象成水龍頭,只不過裡面不是水,而是玻璃球(Value),直徑跟水管的內徑一樣,這樣就能保證玻璃球是依次排列,不會出現並排的情況(資料都是線性處理的,不會出現併發情況)
  2. Subscriber:接受方,可以想象成水龍頭出水口 水龍頭的開關預設是關的,除非有了接收方(Subscriber),才會開啟。這樣只要有新的玻璃球進來,就會自動傳送給接收方。
  3. Filter:過濾器 可以在水龍頭上加一個過濾嘴(Filter),不符合的不讓通過
  4. Map:改動器 可以在水龍頭上加一個改動裝置(Map),把球改變成符合自己的需求

###三、ReactiveCocoa框架各元件 #####1. RACStream類 水管裡面流動的一系列玻璃球,它們有順序的依次通過,在第一個玻璃球沒有到達之前,你沒法獲得第二個玻璃球,RACStream描述的就是這種線性流動玻璃球的形態,比較抽象,是作為描述抽象的父類,它本身的使用意義並不很大,一般會以RACSignal或者RACSequence等這些更高層次的表現形態代替。 ######RACStream的功能總結看下圖:

iOS學習筆記39 ReactiveCocoa入門

#####2. RACSignal類 RAC的核心概念就是Signal,它一般表示未來要到達的值,想象玻璃球一個個從水龍頭裡出來,只有了接收方才能獲取到這些玻璃球。 RACSignal會傳送下面三種事件給它的接受方,想象成水龍頭有個指示燈來彙報它的工作狀態,接受方通過-subscribeNext:error:completed:對不同事件作出相應反應:

  1. Next:從水龍頭裡流出的新玻璃球
  1. Error:獲取新的玻璃球發生了錯誤,一般要傳送一個NSError物件,表明哪裡錯了
  2. Completed:全部玻璃球已經順利抵達,沒有更多的玻璃球加入了

RACSignal可以傳送任意多個Next事件,和一個Error或者Completed事件

iOS學習筆記39 ReactiveCocoa入門

#####3. RACSubject類 RACSubject類,可以認為是“可變的(Mutable)”訊號/自定義訊號,它是嫁接非RAC程式碼到Signal世界的橋樑

iOS學習筆記39 ReactiveCocoa入門

#####4. RACCommand類 RACCommand類,可以認為是迴應某些動作的訊號,通常觸發該訊號的動作都是UI控制元件

iOS學習筆記39 ReactiveCocoa入門

#####5. RACSequence類 RACSequence類,可以簡單看做是RAC世界的NSArray,RAC增加了-rac_sequence方法,可以使諸如NSArray這些集合類直接轉換為RACSequence來使用。

iOS學習筆記39 ReactiveCocoa入門

#####6. RACScheduler類 RACScheduler類,類似於GCD,但RACScheduler類支援撤銷,並且總是執行安全的。 RACScheduler是RAC裡面對執行緒的簡單封裝,事件可以在指定的RACScheduler上分發和執行,不特殊指定的話,事件的分發和執行都在一個預設的後臺執行緒裡面做,大多數情況也就不用動了。 有一些特殊的Signal必須在主執行緒呼叫,使用-deliverOn:可以切換呼叫的執行緒。

iOS學習筆記39 ReactiveCocoa入門

###四、ReactiveCocoa簡單使用20例 #####1. 觀察值變化 ######你別動,你一動我就知道。

//當self.value的值變化時呼叫Block,這是用KVO的機制,RAC封裝了KVO
@weakify(self);
[RACObserve(self, value) subscribeNext:^(NSString* x) {  
	@strongify(self);
	NSLog(@"你動了");
}];
複製程式碼

#####2. 單邊響應 ######你唱歌,我就跳舞。

//建立一個訊號
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	//這個訊號裡面有一個Next事件的玻璃球和一個Complete事件的玻璃球
	[subscriber sendNext:@"唱歌"];
	[subscriber sendCompleted];
	return nil;
}];
//對訊號進行改進,當訊號裡面流的是”唱歌”,就改成”跳舞”返還給self.value
RAC(self, value) = [signalA map:^id(NSString *value) { 
	if ([value isEqualToString:@"唱歌"]) {
		return @"跳舞";
	}
	return @"";
}];
複製程式碼

#####3. 雙邊響應 ######你向西,他就向東,他向左,你就向右。

//建立2個通道,一個從A流出的通道A和一個從B流出的通道B
RACChannelTerminal *channelA = RACChannelTo(self, valueA);
RACChannelTerminal *channelB = RACChannelTo(self, valueB);
//改造通道A,使通過通道A的值,如果等於"西",就改為"東"傳出去
[[channelA map:^id(NSString *value) {
	if ([value isEqualToString:@"西"]) {
		return @"東";
	}	
	return value;
}] subscribe:channelB];//通道A流向B
//改造通道B,使通過通道B的值,如果等於"左",就改為"右"傳出去
[[channelB map:^id(NSString *value) {
	if ([value isEqualToString:@"左"]) {
		return @"右";
	}
	return value;
}] subscribe:channelA];//通道B流向A
//KVO監聽valueA的值得改變,過濾valueA的值,返回YES表示通過
[[RACObserve(self, valueA) filter:^BOOL(id value) {
	return value ? YES : NO;
}] subscribeNext:^(NSString* x) {
	NSLog(@"你向%@", x);
}];
//KVO監聽valueB的值得改變,過濾valueB的值,返回YES表示通過
[[RACObserve(self, valueB) filter:^BOOL(id value) {
	return value ? YES : NO;
}] subscribeNext:^(NSString* x) {
	NSLog(@"他向%@", x);
}];
//下面使valueA的值和valueB的值發生改變
self.valueA = @"西";
self.valueB = @"左";
複製程式碼

######幫助理解,我做了下面這個圖,①②③④是列印順序:

iOS學習筆記39 ReactiveCocoa入門

#####4. 代理 ######你是程式設計師,你幫我寫個app吧。

//代理定義
@protocol ProgrammerDelegate
- (void)makeAnApp;
@end
/****************************************/
//為self新增一個訊號,表示代理ProgrammerDelegate的makeAnApp方法訊號
RACSignal *programmerSignal = [self rac_signalForSelector:@selector(makeAnApp) 
											 fromProtocol:@protocol(ProgrammerDelegate)];
//設定代理方法makeAnApp的實現
[programmerSignal subscribeNext:^(RACTuple* x) {
	//這裡可以理解為makeAnApp的方法要的執行程式碼
    NSLog(@"花了一個月,app寫好了");
}];
//呼叫代理方法
[self makeAnApp];
複製程式碼

#####5. 廣播 #####知道你的頻道,我就能聽到你了。

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
//註冊廣播通知
RACSignal *signal = [center rac_addObserverForName:@"程式碼之道頻道" object:nil];
//設定接收到通知的回撥處理
[signal subscribeNext:^(NSNotification* x) {
	NSLog(@"技巧:%@", x.userInfo[@"技巧"]);
}];
//傳送廣播通知
[center postNotificationName:@"程式碼之道頻道" 
					  object:nil 
				    userInfo:@{@"技巧":@"用心寫"}];
複製程式碼

#####6. 串聯 兩個管串聯,一個管處理完自己的東西,下一個管才開始處理自己的東西 ######生活是一個故事接一個故事。

//建立一個訊號管A
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	//傳送一個Next玻璃球和一個Complete玻璃球
	[subscriber sendNext:@"我戀愛啦"];
	[subscriber sendCompleted];
	return nil;
}];
//建立一個訊號管B
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	//傳送一個Next玻璃球和一個Complete玻璃球
	[subscriber sendNext:@"我結婚啦"];
	[subscriber sendCompleted];
	return nil;
}];
//串聯管A和管B
RACSignal *concatSignal = [signalA concat:signalB];
//串聯後的接收端處理
[concatSignal subscribeNext:^(id x) {
	NSLog(@"%@",x);
}];
//列印:我戀愛啦 我結婚啦
複製程式碼

#####7. 並聯 兩個管並聯,只要有一個管有東西,就拿出來處理它。 ######汙水都應該流入汙水處理廠被處理。

//建立訊號A
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	[subscriber sendNext:@"紙廠汙水"];
	return nil;
}];
//建立訊號B
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	[subscriber sendNext:@"電鍍廠汙水"];
	return nil;
}];
//並聯2個訊號
RACSignal *mergeSignal = [RACSignal merge:@[signalA, signalB]];
[mergeSignal subscribeNext:^(id x) {
	NSLog(@"處理%@",x);
}];
複製程式碼

#####8. 組合

//定義2個自定義訊號
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
//組合訊號
[[RACSignal combineLatest:@[letters, numbers] 
                   reduce:^(NSString *letter, NSString *number){
    //把2個訊號的訊號值進行字串拼接
    return [letter stringByAppendingString:number];
}] subscribeNext:^(NSString * x) {
    NSLog(@"%@", x);
}];
 //自己控制傳送訊號值
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];//列印B1
[letters sendNext:@"C"];//列印C1
[numbers sendNext:@"2"];//列印C2
複製程式碼

iOS學習筆記39 ReactiveCocoa入門
#####9. 合流壓縮 ######我們是紅白的

//建立訊號A
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	[subscriber sendNext:@"紅"];
	[subscriber sendNext:@"白"];
	return nil;
}];
//建立訊號B
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	[subscriber sendNext:@"白"];
	return nil;
}];
//合流後出來的是壓縮包,需要解壓才能取到裡面的值
[[signalA zipWith:signalB] subscribeNext:^(RACTuple* x) {
	//解壓縮
	RACTupleUnpack(NSString *stringA, NSString *stringB) = x;
	NSLog(@"我們是%@%@的", stringA, stringB);
}];
//列印:我們是紅白的
複製程式碼

#####10. 對映 ######我可以點石成金。

//建立訊號,傳送"石"玻璃球
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	[subscriber sendNext:@"石"];
	return nil;
}];
//對訊號進行改造,改造"石"為"金"
signal = [signal map:^id(NSString *value) {
	if ([value isEqualToString:@"石"]) {
		return @"金";
	}
	return value;
}];
//列印
[signal subscribeNext:^(id x) {
	NSLog(@"%@", x);//金
}];
複製程式碼

#####11. 過濾 ######未滿十八歲,禁止進入。

//建立訊號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	[subscriber sendNext:@(15)];
	[subscriber sendNext:@(17)];
	[subscriber sendNext:@(21)];
	[subscriber sendNext:@(14)];
	[subscriber sendNext:@(30)];
	return nil;
}]
//過濾訊號,並列印
[[signal filter:^BOOL(NSNumber* value) {
	//值大於等於18的才能通過過濾網
	return value.integerValue >= 18;
}] subscribeNext:^(id x) {
	NSLog(@"%@", x);
}];
//列印:21 30
複製程式碼

#####12. 秩序(flattenMap方法也可以換成then方法,效果一樣) ######打蛋液,煎雞蛋,上盤。

//建立一個訊號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	NSLog(@"打蛋液");
	[subscriber sendNext:@"蛋液"];
	[subscriber sendCompleted];
	return nil;
}]
//對訊號進行秩序執行第一步
signal = [signal flattenMap:^RACStream *(NSString* value) {
	//處理上一步的RACSignal的訊號值value,這裡value = @"蛋液"
	NSLog(@"把%@倒進鍋裡面煎",value);
	//返回下一步的RACSignal訊號
	return [RACSignal createSignal:^RACDisposable *(id subscriber) {
		[subscriber sendNext:@"煎蛋"];
		[subscriber sendCompleted];
		return nil;
	}];
}];
//對訊號進行秩序執行第二步
signal = [signal flattenMap:^RACStream *(NSString* value) {
	//處理上一步的RACSignal的訊號值value,這裡value = @"煎蛋"
	NSLog(@"把%@裝到盤裡", value);
	//返回下一步的RACSignal訊號
	return [RACSignal createSignal:^RACDisposable *(id subscriber) {
		[subscriber sendNext:@"上菜"];
		[subscriber sendCompleted];
		return nil;
	}];
}];
//最後列印
[signal subscribeNext:^(id x) {
	NSLog(@"%@", x);
}];
/* 
列印:
	打蛋液  
	把蛋液倒進鍋裡面煎  
	把煎蛋裝到盤裡  
	上菜
*/
複製程式碼

#####13. 命令 ######我命令你馬上投降。

//建立命令
RACCommand *aCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSString *input) {
	//命令執行程式碼
	NSLog(@"%@我投降了",input);
	//返回一個RACSignal訊號
	return [RACSignal createSignal:^RACDisposable *(id subscriber) {
		[subscriber sendCompleted];
		return nil;
	}];
}];
//執行命令
[aCommand execute:@"今天"];
//列印:今天我投降了
複製程式碼

#####14. 延遲 ######等等我,我還有10秒鐘就到了。

//建立一個訊號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	NSLog(@"等等我,我還有10秒鐘就到了");
	[subscriber sendNext:@"車陂南"];
	[subscriber sendCompleted];
	return nil;
}];
//延時10秒接受Next玻璃球
[[signal delay:10] subscribeNext:^(NSString *x) {
	NSLog(@"我到了%@",x);
}];
/*
[2016-04-21 13:20:10]等等我,我還有10秒鐘就到了
[2016-04-21 13:20:20]我到了車陂南
*/
複製程式碼

#####15. 重放 ######一次製作,多次觀看。

//建立一個普通訊號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	NSLog(@"大導演拍了一部電影《我的男票是程式設計師》");
	[subscriber sendNext:@"《我的男票是程式設計師》"];
	return nil;
}];
//建立該普通訊號的重複訊號
RACSignal *replaySignal = [signal replay];
//重複接受訊號
[replaySignal subscribeNext:^(NSString *x) {
	NSLog(@"小明看了%@", x);
}];
[replaySignal subscribeNext:^(NSString *x) {
	NSLog(@"小紅也看了%@", x);
}];
/*
大導演拍了一部電影《我的男票是程式設計師》
小明看了《我的男票是程式設計師》
小紅也看了《我的男票是程式設計師》
*/
複製程式碼

#####16. 定時 ######每隔8個小時服一次藥。

//建立定時器訊號,定時8個小時
RACSignal *signal = [RACSignal interval:60*60*8 
							onScheduler:[RACScheduler mainThreadScheduler]];
//定時執行程式碼
[signal subscribeNext:^(id x) {
	NSLog(@"吃藥");
}];
複製程式碼

#####17. 超時 ######等了你一個小時了,你還沒來,我走了。

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	//建立傳送資訊訊號
	NSLog(@"我快到了");
	RACSignal *sendSignal = [RACSignal createSignal:^RACDisposable *(id sendSubscriber) {
		[sendSubscriber sendNext:nil];
		[sendSubscriber sendCompleted];
		return nil;
	}];
	//傳送資訊要1個小時10分鐘才到
	[[sendSignal delay:60*70] subscribeNext:^(id x) {
		//這裡才傳送Next玻璃球到signal
		[subscriber sendNext:@"我到了"];
		[subscriber sendCompleted];
	}];
	return nil;
}]
//這裡對signal進行超時接受處理,如果1個小時都沒收到玻璃球,超時錯誤
[[signal timeout:60*60 
     onScheduler:[RACScheduler mainThreadScheduler]] 
  subscribeError:^(NSError *error)
{
	//超時錯誤處理
	NSLog(@"等了你一個小時了,你還沒來,我走了");
}];
複製程式碼

#####18. 重試 ######成功之前可能需要數百次失敗。

__block int failedCount = 0;
//建立訊號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	if (failedCount < 100) {
		failedCount++;
		NSLog(@"我失敗了");
		//傳送錯誤,才會要重試
		[subscriber sendError:nil];
	} else {
		NSLog(@"經歷了數百次失敗後");
		[subscriber sendNext:nil];
	}
	return nil;
}];
//重試
RACSignal *retrySignal = [signal retry];
//直到傳送了Next玻璃球
[retrySignal subscribeNext:^(id x) {
	NSLog(@"終於成功了");
}];
複製程式碼

#####19. 節流 ######不好意思,這裡一秒鐘只能通過一個人。

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	//即時傳送一個Next玻璃球
	[subscriber sendNext:@"旅客A"];
	//下面是GCD延時傳送Next玻璃球
	dispatch_queue_t mainQueue = dispatch_get_main_queue();
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(1 * NSEC_PER_SEC)),mainQueue, ^{
		[subscriber sendNext:@"旅客B"];
	});
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2 * NSEC_PER_SEC)),mainQueue, ^{
		//傳送多個Next,如果節流了,接收最新傳送的
		[subscriber sendNext:@"旅客C"];
		[subscriber sendNext:@"旅客D"];
		[subscriber sendNext:@"旅客E"];
	});
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(3 * NSEC_PER_SEC)),mainQueue, ^{
		[subscriber sendNext:@"旅客F"];
	});
	return nil;
}];
//對訊號進行節流,限制短時間內一次只能接收一個Next玻璃球
[[signal throttle:1] subscribeNext:^(id x) {
	NSLog(@"%@通過了",x);
}];
/*
[2015-08-16 22:08:45.677]旅客A  
[2015-08-16 22:08:46.737]旅客B  
[2015-08-16 22:08:47.822]旅客E  
[2015-08-16 22:08:48.920]旅客F
*/
複製程式碼

#####20. 條件(takeUntil方法:當給定的signal完成前一直取值) ######直到世界的盡頭才能把我們分開。

//建立取值訊號
RACSignal *takeSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
	//建立一個定時器訊號,每隔1秒觸發一次
	RACSignal *signal = [RACSignal interval:1 
								onScheduler:[RACScheduler mainThreadScheduler]];
	//定時接收
	[signal subscribeNext:^(id x) {
		//在這裡定時傳送Next玻璃球
		[subscriber sendNext:@"直到世界的盡頭才能把我們分開"];
	}];
	return nil;
}];
//建立條件訊號
RACSignal *conditionSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    //設定5秒後發生Complete玻璃球
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 
          (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
		NSLog(@"世界的盡頭到了");
		[subscriber sendCompleted];
	});
	return nil;
}]
//設定條件,takeSignal訊號在conditionSignal訊號接收完成前,不斷地取值
[[takeSignal takeUntil:conditionSignal] subscribeNext:^(id x) {
	NSLog(@"%@", x);
}];
/*
[2015-08-16 22:17:22.648]直到世界的盡頭才能把我們分開  
[2015-08-16 22:17:23.648]直到世界的盡頭才能把我們分開  
[2015-08-16 22:17:24.645]直到世界的盡頭才能把我們分開  
[2015-08-16 22:17:25.648]直到世界的盡頭才能把我們分開  
[2015-08-16 22:17:26.644]直到世界的盡頭才能把我們分開  
[2015-08-16 22:17:26.645]世界的盡頭到了
*/
複製程式碼

RAC還有更多的使用,目前就學習到這些,大部分功能基本已經符合我們的開發要求了,就整理分享出來。

iOS學習筆記39 ReactiveCocoa入門

#####如果有什麼問題在下方評論區中提出!O(∩_∩)O哈!

相關文章