1. 程式人生 > >RAC(ReactiveCocoa)學習資源彙總~持續更新

RAC(ReactiveCocoa)學習資源彙總~持續更新

朋友介紹RAC不錯,在git上一搜,竟然有一萬三的星星,遂下下來試下水,發現非常好用,無奈程式設計思想還不太習慣,裡面要學的東西也很多,所以,這次特意開一個專題用來學習,記錄自己學習RAC的旅程;

今天看了一篇部落格,對照著上面的知識點全部手敲了一遍程式碼,對於新知識我一向秉承多看不如多敲的思想(主要是因為本人比較笨啦,看的記不住,O(∩_∩)O)

03~15 03~17 RAC的學習今天暫告一段落 , 接下來開始RAC的使用了,以下貼上整個學習的相關程式碼,等我整理好後將上傳到git上

- (NSArray *)dataArrString{

if (!

_dataArrString){

_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(@"%@"

,x);

    }];

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監聽屬性的改變,使用blockKVO

//    [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.建立connectconnect.sourceSignal -> RACSignal(原始訊號)  connect.signal -> RACSubject

     2.訂閱connect.signal,會呼叫RACSubjectsubscribeNext,建立訂閱者,而且把訂閱者儲存起來,不會執行block

     3.[connect connect]內部會訂閱RACSignal(原始訊號),並且訂閱者是RACSubject

         3.1.訂閱原始訊號,就會呼叫原始訊號中的didSubscribe

         3.2 didSubscribe,拿到訂閱者呼叫sendNext,其實是呼叫RACSubjectsendNext

     4.RACSubjectsendNext,會遍歷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.字典轉模型

     RACSequenceRACTuple簡單使用

     */

// 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.當訊號被訂閱,也就是呼叫signalsubscribeNext:nextBlock

         2.2 subscribeNext內部會建立訂閱者subscriber,並且把nextBlock儲存到subscriber中。

         2.1 subscribeNext內部會呼叫siganldidSubscribe

     3.siganldidSubscribe中呼叫[subscriber sendNext:@1];

         3.1 sendNext底層其實就是執行subscribernextBlock

如果不在傳送資料,最好傳送訊號完成(sendCompleted),內部會自動呼叫[RACDisposable disposable]取消訂閱訊號。

     [subscriber sendCompleted];

     RACSubscriber:表示訂閱者的意思,用於傳送訊號,這是一個協議,不是一個類,只要遵守這個協議,並且實現方法才能成為訂閱者。通過create建立的訊號,都有一個訂閱者,幫助他傳送資料

     RACDisposable:用於取消訂閱或者清理資源,當訊號傳送完成或者傳送錯誤的時候,就會自動觸發它

使用場景:不想監聽某個訊號時,可以通過它主動取消訂閱訊號

     RACSubject:RACSubject:訊號提供者,自己可以充當訊號,又能傳送訊號

使用場景:通常用來代替代理,有了它,就不必要定義代理了

       RACReplaySubject:重複提供訊號類,RACSubject的子類

       RACReplaySubjectRACSubject區別:

       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:把呼叫某個物件的方法的資訊轉換