RAC(ReactiveCocoa)學習資源彙總~持續更新
朋友介紹RAC不錯,在git上一搜,竟然有一萬三的星星,遂下下來試下水,發現非常好用,無奈程式設計思想還不太習慣,裡面要學的東西也很多,所以,這次特意開一個專題用來學習,記錄自己學習RAC的旅程;
今天看了一篇部落格,對照著上面的知識點全部手敲了一遍程式碼,對於新知識我一向秉承多看不如多敲的思想(主要是因為本人比較笨啦,看的記不住,O(∩_∩)O)
03~15 03~17 RAC的學習今天暫告一段落 , 接下來開始RAC的使用了,以下貼上整個學習的相關程式碼,等我整理好後將上傳到git上
- (NSArray *)dataArrString{
if (!
_dataArrString = [@"A B C D E F G H I"componentsSeparatedByString:@" "];
}
return_dataArrString;
}
- (NSArray *)dataArrNumber{
if (!_dataArrNumber){
_dataArrNumber = [@"1 2 3 4 5 6 7 8 9"componentsSeparatedByString:@" "];
}
return_dataArrNumber;
}
- (void)viewDidLoad {
[superviewDidLoad];
[selftestAllRAC];
return;
self.signInService = [RWDummySignInServicenew];
self.signInFailureText.hidden =YES;
[[self.usernameTextField.rac_textSignalfilter:^BOOL(NSString* value) {
return value.length >3;
}] subscribeNext:^(id x) {
NSLog(@"%@"
}];
RACSignal* validUsernameSignal = [self.usernameTextField.rac_textSignalmap:^id(NSString* text) {
return@([selfisValidUsername:text]);
}];
RAC(self.usernameTextField , backgroundColor) = [validUsernameSignalmap:^id(NSNumber *usernameValid) {
return [usernameValidboolValue] ? [UIColorclearColor]:[UIColoryellowColor];
}];
[[[[self.signInButtonrac_signalForControlEvents:UIControlEventTouchUpInside]doNext:^(id x) {
self.signInButton.enabled =NO;
self.signInFailureText.hidden =YES;
}] flattenMap:^id(id value) {
return [selfsignInSignal];
}]
subscribeNext:^(NSNumber*signedIn) {
self.signInFailureText.enabled =YES;
BOOL success =[signedInboolValue];
self.signInFailureText.hidden = success;
if(success){
[selfperformSegueWithIdentifier:@"signInSuccess"sender:self];
}
}];
[selfusernameTextFieldChanged];
[selfpasswordTextFieldChanged];
[selfupdateUIState];
}
- (void)testRegister{
[RACObserve(self, usernameTextField.text)subscribeNext:^(NSString* text) {
// NSLog(@"text : %@",text);
}];
[[RACObserve(self.usernameTextField, text)filter:^BOOL(NSString* text) {
returnYES;
}] subscribeNext:^(NSString* text) {
// NSLog(@"text2 : %@",text);
}];
//Signals能夠用來匯出狀態,RAC通過signals and operations讓表示屬性變得有可能
//連線兩個屬性的值,只有當兩個屬性的值滿足一定的條件時,按鈕才可以被點選
RAC(self , signInButton.enabled) = [RACSignalcombineLatest:@[RACObserve(self.usernameTextField, text),RACObserve(self.passwordTextField, text)]reduce:^(NSString* userName ,NSString* password){
// NSLog(@"username : %@ password : %@",userName , password);
return@(![userName isEqualToString:password]);
}];
// 按鈕的點選
self.signInButton.rac_command = [[RACCommandalloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"signInButton button was pressed!");
return [RACSignalempty];
}];
}
- (void)testAllRAC{
[selftestRegister];
RACSequence* letters =self.dataArrString.rac_sequence;
RACSignal* letter = letters.signal;
// 依次輸出 A B C D…
[letter subscribeNext:^(NSString* text) {
// NSLog(@"text %@",text);
}];
// 測試注入效果 -doNext: -doError: -doCompleted:
// [self testNextAndCompleted];
// 測試 map:對映,可以看做對玻璃球的變換、重新組裝
// [self testMap];
// 測試 filter:過濾,不符合要求的玻璃球不允許通過
// [self testFilter];
// 測試 concat:把一個水管拼接到另一個水管之後
// [self testConcat];
// 測試 flatten :
// [self testFlatten];
// 測試 map and flatten
// [self testMapAndFlatten];
// 測試 combine
// [self testCombining];
// 測試 switching
// [self testSwitching];
// 測試RAC RAC可以看作某個屬性的值與一些訊號的聯動
// [self testRAC];
// 測試 RACObserve監聽屬性的改變,使用block的KVO
// [self testRACObserve];
// 網路測試
// [self testNetwork];
// 實現一個時鐘應用
// [self testClockApplication];
// 測試組合 combine
// [self testCombine];
// 測試 RACSubject的使用
// [self testSubject];
// 測試 RACReplaySubject的使用
// [self testReplaySubject];
// 測試 RACTuple和 RACSequence 的使用
// [self testSequenceAndTuple];
// 測試 RACCommand :用於處理事件的類
// [self testCommand];
// 測試 RACMulticastConnection的使用
// [self testMulticastConnection];
// 測試 RAC開發中常見用法
// [self testRACMethod];
// 測試 RAC開發中常見巨集
[selftestMacroDefinition];
}
#pragma mark - 測試 RAC開發中常見巨集
- (void)testMacroDefinition{
/*
8.1 RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用於給某個物件的某個屬性繫結
// 只要文字框文字改變,就會修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;
8.2 RACObserve(self, name):監聽某個物件的某個屬性,返回的是訊號
[RACObserve(self.view, center) subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
8.3 @weakify(Obj)和@strongify(Obj),一般兩個都是配套使用,在主標頭檔案(ReactiveCocoa.h)中並沒有匯入,需要自己手動匯入,RACEXTScope.h才可以使用。但是每次匯入都非常麻煩,只需要在主標頭檔案自己匯入就好了
8.4 RACTuplePack:把資料包裝成RACTuple(元組類)
// 把引數中的資料包裝成元組
RACTuple *tuple = RACTuplePack(@10,@20);
8.5 RACTupleUnpack:把RACTuple(元組類)解包成對應的資料
// 把引數中的資料包裝成元組
RACTuple *tuple = RACTuplePack(@"xmg",@20);
// 解包元組,會把元組的值,按順序給引數裡面的變數賦值
// name = @"xmg" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
*/
}
#pragma mark - 測試 RACMulticastConnection的使用 : 用於當一個訊號,被多次訂閱時,為了保證建立訊號時,避免多次呼叫建立訊號中的block,造成副作用,可以使用這個類處理
- (void)testMulticastConnection{
// 使用注意:RACMulticastConnection通過RACSignal的-publish或者-muticast:方法建立
/*
RACMulticastConnection使用步驟:
1.建立訊號 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
2.建立連線 RACMulticastConnection *connect = [signal publish];
3.訂閱訊號,注意:訂閱的不在是之前的訊號,而是連線的訊號。 [connect.signal subscribeNext:nextBlock]
4.連線 [connect connect]
RACMulticastConnection底層原理:
1.建立connect,connect.sourceSignal -> RACSignal(原始訊號) connect.signal -> RACSubject
2.訂閱connect.signal,會呼叫RACSubject的subscribeNext,建立訂閱者,而且把訂閱者儲存起來,不會執行block。
3.[connect connect]內部會訂閱RACSignal(原始訊號),並且訂閱者是RACSubject
3.1.訂閱原始訊號,就會呼叫原始訊號中的didSubscribe
3.2 didSubscribe,拿到訂閱者呼叫sendNext,其實是呼叫RACSubject的sendNext
4.RACSubject的sendNext,會遍歷RACSubject所有訂閱者傳送訊號。
4.1 因為剛剛第二步,都是在訂閱RACSubject,因此會拿到第二步所有的訂閱者,呼叫他們的nextBlock
需求:假設在一個訊號中傳送請求,每次訂閱一次都會發送請求,這樣就會導致多次請求。
解決:使用RACMulticastConnection就能解決
*/
// 1.建立請求訊號
RACSignal* signal = [RACSignalcreateSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"傳送請求");
[subscriber sendNext:@1];
returnnil;
}];
// 2.訂閱訊號
[signal subscribeNext:^(id x) {
NSLog(@"接收資料");
}];
// 2.訂閱訊號
[signal subscribeNext:^(id x) {
NSLog(@"接收資料");
}];
// return;
// 3.執行結果,會執行兩遍傳送請求,也就是每次訂閱都會發送一次請求
// RACMulticastConnection:解決重複請求問題
// 1.建立訊號
RACSignal* signal2 = [RACSignalcreateSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"再次傳送請求");
[subscriber sendNext:@1];
returnnil;
}];
// 2.建立連線
RACMulticastConnection* connect = [signal2publish];
// 3.訂閱訊號,
// 注意:訂閱訊號,也不能啟用訊號,只是儲存訂閱者到陣列,必須通過連線,當呼叫連線,就會一次性呼叫所有訂閱者的sendNext:
[connect.signalsubscribeNext:^(id x) {
NSLog(@"訂閱者一訊號 %@",x);
}];
[connect.signalsubscribeNext:^(id x) {
NSLog(@"訂閱者二訊號 %@",x);
}];
// 4.連線,啟用訊號
[connect connect];
}
#pragma mark - 測試 RACCommand :用於處理事件的類
- (void)testCommand{
/*
RACCommand:RAC中用於處理事件的類,可以把事件如何處理,事件中的資料如何傳遞,包裝到這個類中,他可以很方便的監控事件的執行過程
一、RACCommand使用步驟:
1.建立命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
2.在signalBlock中,建立RACSignal,並且作為signalBlock的返回值
3.執行命令 - (RACSignal *)execute:(id)input
二、RACCommand使用注意:
1.signalBlock必須要返回一個訊號,不能傳nil.
2.如果不想要傳遞訊號,直接建立空的訊號[RACSignal empty];
3.RACCommand中訊號如果資料傳遞完,必須呼叫[subscriber sendCompleted],這時命令才會執行完畢,否則永遠處於執行中。
4.RACCommand需要被強引用,否則接收不到RACCommand中的訊號,因此RACCommand中的訊號是延遲傳送的
三、RACCommand設計思想:內部signalBlock為什麼要返回一個訊號,這個訊號有什麼用。
1.在RAC開發中,通常會把網路請求封裝到RACCommand,直接執行某個RACCommand就能傳送請求。
2.當RACCommand內部請求到資料的時候,需要把請求的資料傳遞給外界,這時候就需要通過signalBlock返回的訊號傳遞了
四、如何拿到RACCommand中返回訊號發出的資料。
1.RACCommand有個執行訊號源executionSignals,這個是signal of signals(訊號的訊號),意思是訊號發出的資料是訊號,不是普通的型別。
2.訂閱executionSignals就能拿到RACCommand中返回的訊號,然後訂閱signalBlock返回的訊號,就能獲取發出的值
五、監聽當前命令是否正在執行executing
六、使用場景,監聽按鈕點選,網路請求
*/
// 1. 建立命令
RACCommand* command = [[RACCommandalloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"執行命令");
// 建立空訊號,必須返回訊號
// return [RACSignal empty];
// 2.建立訊號,用來傳遞資料
return [RACSignalcreateSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"請求資料"];
// 注意:資料傳遞完,最好呼叫sendCompleted,這時命令才執行完畢
[subscriber sendCompleted];
returnnil;
}];
}];
// 強引用命令,不要被銷燬,否則接收不到資料
_command = command;
// 3.訂閱RACCommand中的訊號
[command.executionSignalssubscribeNext:^(id x) {
[x subscribeNext:^(id x) {
NSLog(@"x : %@",x);
}];
}];
// RAC高階用法
// switchToLatest:用於signal of signals,獲取signal of signals發出的最新訊號,也就是可以直接拿到RACCommand中的訊號
[command.executionSignals.switchToLatestsubscribeNext:^(id x) {
NSLog(@"switchToLatest : %@",x);
}];
// 4.監聽命令是否執行完畢,預設會來一次,可以直接跳過,skip表示跳過第一次訊號
[[command.executingskip:1]subscribeNext:^(id x) {
if ([xboolValue] == YES){
// 正在執行
NSLog(@"正在執行");
}else{
// 執行完成
NSLog(@"執行完成");
}
}];
// 5. 執行命令
[self.commandexecute:@1];
}
#pragma mark - 測試 RACTuple和 RACSequence 的使用
- (void)testSequenceAndTuple{
/*
RACTuple:元組類,類似NSArray,用來包裝值
RACSequence:RAC中的集合類,用於代替NSArray,NSDictionary,可以使用它來快速遍歷陣列和字典
使用場景:1.字典轉模型
RACSequence和RACTuple簡單使用
*/
// 1.遍歷陣列
NSArray *numbers =@[@1,@2,@3,@4];
// 這裡其實是三步
// 第一步:把陣列轉換成集合RACSequence numbers.rac_sequence
// 第二步:把集合RACSequence轉換RACSignal訊號類,numbers.rac_sequence.signal
// 第三步:訂閱訊號,啟用訊號,會自動把集合中的所有值,遍歷出來。
[numbers.rac_sequence.signalsubscribeNext:^(id x) {
NSLog(@"x : %@",x);
}];
// 2.遍歷字典,遍歷出來的鍵值對會包裝成RACTuple(元組物件)
NSDictionary *dict =@{@"name":@"xmg",@"age":@18};
[dict.rac_sequence.signalsubscribeNext:^(RACTuple *x) {
// 解包元組,會把元組的值,按順序給引數裡面的變數賦值
RACTupleUnpack(NSString *key,NSString *value) = x;
// 相當於以下寫法
// NSString *key = x[0];
// NSString *value = x[1];
NSLog(@"key : %@ value : %@",key,value);
}];
#pragma mark - 3.字典轉模型
#pragma mark - 3.1 OC寫法
/*
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
NSMutableArray *items = [NSMutableArray array];
for (NSDictionary *dict in dictArr) {
FlagItem *item = [FlagItem flagWithDict:dict];
[items addObject:item];
}
*/
#pragma mark - 3.2 RAC寫法
/*
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
NSMutableArray *flags = [NSMutableArray array];
_flags = flags;
// rac_sequence注意點:呼叫subscribeNext,並不會馬上執行nextBlock,而是會等一會。
[dictArr.rac_sequence.signal subscribeNext:^(id x) {
// 運用RAC遍歷字典,x:字典
FlagItem *item = [FlagItem flagWithDict:x];
[flags addObject:item];
}];
*/
#pragma mark - 3.3 RAC高階寫法:
/*
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
// map:對映的意思,目的:把原始值value對映成一個新值
// array: 把集合轉換成陣列
// 底層實現:當訊號被訂閱,會遍歷集合中的原始值,對映成新值,並且儲存到新的數組裡。
NSArray *flags = [[dictArr.rac_sequence map:^id(id value) {
return [FlagItem flagWithDict:value];
}] array];
*/
}
/*
需求:
1.給當前控制器新增一個按鈕,modal到另一個控制器介面
2.另一個控制器view中有個按鈕,點選按鈕,通知當前控制器
*/
#pragma mark - RACSubject替換代理
- (void)testSubjectReplaceDelegate{
/*
步驟一:在第二個控制器.h,新增一個RACSubject代替代理。
@interface TwoViewController : UIViewController
@property (nonatomic, strong) RACSubject *delegateSignal;
@end
步驟二:監聽第二個控制器按鈕點選
@implementation TwoViewController
- (IBAction)notice:(id)sender {
// 通知第一個控制器,告訴它,按鈕被點了
// 通知代理
// 判斷代理訊號是否有值
if (self.delegateSignal) {
// 有值,才需要通知
[self.delegateSignal sendNext:nil];
}
}
@end
步驟三:在第一個控制器中,監聽跳轉按鈕,給第二個控制器的代理訊號賦值,並且監聽.
@implementation OneViewController
- (IBAction)btnClick:(id)sender {
// 建立第二個控制器
TwoViewController *twoVc = [[TwoViewController alloc] init];
// 設定代理訊號
twoVc.delegateSignal = [RACSubject subject];
// 訂閱代理訊號
[twoVc.delegateSignal subscribeNext:^(id x) {
NSLog(@"點選了通知按鈕");
}];
// 跳轉到第二個控制器
[self presentViewController:twoVc animated:YES completion:nil];
}
@end
*/
}
#pragma mark - RAC部分知識彙總
- (void)RACSomeKnowledge{
/*
1. 如何取消訂閱一個signal?在一個completed或者error事件之後,訂閱會自動移除)
2. 你還可以通過RACDisposable手動移除訂閱,RACSignal的訂閱方法都會返回一個RACDisposable例項,它能讓你通過dispose方法手動移除訂閱
3. 如果你建立了一個管道,但是沒有訂閱它,這個管道就不會執行,包括任何如doNext: block的附加操作
*/
/*
RACSignal使用步驟:
1.建立訊號 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
2.訂閱訊號,才會啟用訊號. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
3.傳送訊號 - (void)sendNext:(id)value
RACSignal底層實現:
1.建立訊號,首先把didSubscribe儲存到訊號中,還不會觸發。
2.當訊號被訂閱,也就是呼叫signal的subscribeNext:nextBlock
2.2 subscribeNext內部會建立訂閱者subscriber,並且把nextBlock儲存到subscriber中。
2.1 subscribeNext內部會呼叫siganl的didSubscribe
3.siganl的didSubscribe中呼叫[subscriber sendNext:@1];
3.1 sendNext底層其實就是執行subscriber的nextBlock
如果不在傳送資料,最好傳送訊號完成(sendCompleted),內部會自動呼叫[RACDisposable disposable]取消訂閱訊號。
[subscriber sendCompleted];
RACSubscriber:表示訂閱者的意思,用於傳送訊號,這是一個協議,不是一個類,只要遵守這個協議,並且實現方法才能成為訂閱者。通過create建立的訊號,都有一個訂閱者,幫助他傳送資料
RACDisposable:用於取消訂閱或者清理資源,當訊號傳送完成或者傳送錯誤的時候,就會自動觸發它
使用場景:不想監聽某個訊號時,可以通過它主動取消訂閱訊號
RACSubject:RACSubject:訊號提供者,自己可以充當訊號,又能傳送訊號
使用場景:通常用來代替代理,有了它,就不必要定義代理了
RACReplaySubject:重複提供訊號類,RACSubject的子類
RACReplaySubject與RACSubject區別:
RACReplaySubject可以先發送訊號,在訂閱訊號,RACSubject就不可以。
使用場景一:如果一個訊號每被訂閱一次,就需要把之前的值重複傳送一遍,使用重複提供訊號類。
使用場景二:可以設定capacity數量來限制快取的value的數量,即只緩充最新的幾個值
[self testSubject];
[self testReplaySubject];
RACScheduler:RAC中的佇列,用GCD封裝的
RACUnit :表⽰stream不包含有意義的值,也就是看到這個,可以直接理解為nil
RACEvent: 把資料包裝成訊號事件(signal event)。它主要通過RACSignal的-materialize來使用,然並卵
*/
/*
#pragma mark - ReactiveCocoa開發中常見用法
rac_signalForSelector:用於替代代理
rac_valuesAndChangesForKeyPath:用於監聽某個物件的屬性改變
rac_signalForControlEvents:用於監聽某個事件
rac_addObserverForName:用於監聽某個通知
rac_textSignal:只要文字框發出改變就會發出這個訊號
rac_liftSelector:withSignalsFromArray:Signals:當傳入的Signals(訊號陣列),每一個signal都至少sendNext過一次,就會去觸發第一個selector引數的方法使用注意:幾個訊號,引數一的方法就幾個引數,每個引數對應訊號發出的資料
// 程式碼演示
[self testRACMethod];
*/
}
- (void)btnClick:(UIButton* )button{
NSLog(@"btnClick");
}
#pragma mark - ReactiveCocoa開發中常見用法
- (void)testRACMethod{
UIView* redV = [[UIViewalloc] initWithFrame:CGRectMake(100,100, 100,100)];
_redV = redV;
// [self.view addSubview:redV];
// 1.代替代理
// 需求:自定義redView,監聽紅色view中按鈕點選
// 之前都是需要通過代理監聽,給紅色View新增一個代理屬性,點選按鈕的時候,通知代理做事情
// rac_signalForSelector:把呼叫某個物件的方法的資訊轉換