ReactiveCocoa進階

轉載自最快讓你上手ReactiveCocoa之進階篇,稍做修改javascript

ReactiveCocoa進階思惟導圖
ReactiveCocoa進階思惟導圖

常見操做方法介紹

操做須知

全部的信號(RACSignal)均可以進行操做處理,由於全部操做方法都定義在RACStream.h中,所以只要繼承RACStream就有了操做處理方法。css

操做思想

運用的是Hook(鉤子)思想,Hook是一種用於改變API(應用程序編程接口:方法)執行結果的技術.html

Hook用處:截獲API調用的技術。java

有關Hook的知識能夠看個人這篇博客《Objective-C Runtime 的一些基本使用》中的 更換代碼的實現方法 一節,ios

Hook原理:在每次調用一個API返回結果以前,先執行你本身的方法,改變結果的輸出。git

操做方法

bind(綁定)- ReactiveCocoa核心方法

ReactiveCocoa 操做的核心方法是 bind(綁定),並且也是RAC中核心開發方式。以前的開發方式是賦值,而用RAC開發,應該把重心放在綁定,也就是能夠在建立一個對象的時候,就綁定好之後想要作的事情,而不是等賦值以後在去作事情。github

列如,把數據展現到控件上,以前都是重寫控件的 setModel 方法,用RAC就能夠在一開始建立控件的時候,就綁定好數據。編程

  • 做用api

    RAC底層都是調用bind, 在開發中不多直接使用 bind 方法,bind屬於RAC中的底層方法,咱們只須要調用封裝好的方法,bind用做了解便可.數組

  • bind方法使用步驟

    1. 傳入一個返回值 RACStreamBindBlock 的 block。
    2. 描述一個 RACStreamBindBlock 類型的 bindBlock做爲block的返回值。
    3. 描述一個返回結果的信號,做爲 bindBlock 的返回值。

    注意:在bindBlock中作信號結果的處理。

  • bind方法參數

    RACStreamBindBlock:
    typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop);

    參數一(value):表示接收到信號的原始值,還沒作處理

    參數二(*stop):用來控制綁定Block,若是*stop = yes,那麼就會結束綁定。

    返回值:信號,作好處理,在經過這個信號返回出去,通常使用 RACReturnSignal,須要手動導入頭文件RACReturnSignal.h

  • 使用

    假設想監聽文本框的內容,而且在每次輸出結果的時候,都在文本框的內容拼接一段文字「輸出:」

    • 使用封裝好的方法:在返回結果後,拼接。

      [_textField.rac_textSignal subscribeNext:^(id x) { // 在返回結果後,拼接 輸出: NSLog(@"輸出:%@",x); }]; 
    • 使用RAC中 bind 方法作處理,在返回結果前,拼接。

    這裏須要手動導入#import <ReactiveCocoa/RACReturnSignal.h>,才能使用RACReturnSignal

    ``` 
      [[_textField.rac_textSignal bind:^RACStreamBindBlock{
         // 何時調用: // block做用:表示綁定了一個信號. return ^RACStream *(id value, BOOL *stop){ // 何時調用block:當信號有新的值發出,就會來到這個block。 // block做用:作返回值的處理 // 作好處理,在返回結果前,拼接 輸出: return [RACReturnSignal return:[NSString stringWithFormat:@"輸出:%@",value]]; }; }] subscribeNext:^(id x) { NSLog(@"%@",x); }]; ``` 
  • 底層實現

    1. 源信號調用bind,會從新建立一個綁定信號。
    2. 當綁定信號被訂閱,就會調用綁定信號中的 didSubscribe ,生成一個 bindingBlock
    3. 當源信號有內容發出,就會把內容傳遞到 bindingBlock 處理,調用bindingBlock(value,stop)
    4. 調用bindingBlock(value,stop),會返回一個內容處理完成的信號RACReturnSignal
    5. 訂閱RACReturnSignal,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。

    注意:不一樣訂閱者,保存不一樣的nextBlock,看源碼的時候,必定要看清楚訂閱者是哪一個。

映射

映射主要用這兩個方法實現:flattenMap,Map,用於把源信號內容映射成新的內容。

flattenMap
  • 做用

    把源信號的內容映射成一個新的信號,信號能夠是任意類型

  • 使用步驟

    1. 傳入一個block,block類型是返回值RACStream,參數value
    2. 參數value就是源信號的內容,拿到源信號的內容作處理
    3. 包裝成RACReturnSignal信號,返回出去。
  • 使用

    監聽文本框的內容改變,把結構從新映射成一個新值.

    [[_textField.rac_textSignal flattenMap:^RACStream *(id value) { // block調用時機:信號源發出的時候 // block做用:改變信號的內容 // 返回RACReturnSignal return [RACReturnSignal return:[NSString stringWithFormat:@"信號內容:%@", value]]; }] subscribeNext:^(id x) { NSLog(@"%@", x); }]; 
  • 底層實現

    1. flattenMap內部調用 bind 方法實現的,flattenMap中block的返回值,會做爲bind中bindBlock的返回值。
    2. 當訂閱綁定信號,就會生成 bindBlock
    3. 當源信號發送內容,就會調用bindBlock(value, *stop)
    4. 調用bindBlock,內部就會調用 flattenMap 的 bloc k,flattenMap 的block做用:就是把處理好的數據包裝成信號。
    5. 返回的信號最終會做爲 bindBlock 中的返回信號,當作 bindBlock 的返回信號。
    6. 訂閱 bindBlock 的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。
Map
  • 做用

    把源信號的值映射成一個新的值

  • 使用步驟

    1. 傳入一個block,類型是返回對象,參數是 value
    2. value就是源信號的內容,直接拿到源信號的內容作處理
    3. 把處理好的內容,直接返回就行了,不用包裝成信號,返回的值,就是映射的值。
  • 使用

    監聽文本框的內容改變,把結構從新映射成一個新值.

    [[_textField.rac_textSignal map:^id(id value) { // 拼接完後,返回對象 return [NSString stringWithFormat:@"信號內容: %@", value]; }] subscribeNext:^(id x) { NSLog(@"%@", x); }]; 
  • 底層實現:

    1. Map底層實際上是調用 flatternMap,Map 中block中的返回的值會做爲 flatternMap 中block中的值
    2. 當訂閱綁定信號,就會生成 bindBlock
    3. 當源信號發送內容,就會調用 bindBlock(value, *stop)
    4. 調用 bindBlock ,內部就會調用 flattenMap的block
    5. flattenMap的block 內部會調用 Map 中的block,把 Map 中的block返回的內容包裝成返回的信號
    6. 返回的信號最終會做爲 bindBlock 中的返回信號,當作 bindBlock 的返回信號
    7. 訂閱 bindBlock 的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。
FlatternMap 和 Map 的區別
  • FlatternMap 中的Block 返回信號
  1. Map 中的Block 返回對象
  2. 開發中,若是信號發出的值 不是信號 ,映射通常使用 Map
  3. 若是信號發出的值 是信號,映射通常使用 FlatternMap
  • signalOfsignalsFlatternMap

    // 建立信號中的信號 RACSubject *signalOfsignals = [RACSubject subject]; RACSubject *signal = [RACSubject subject]; [[signalOfsignals flattenMap:^RACStream *(id value) { // 當signalOfsignals的signals發出信號纔會調用 return value; }] subscribeNext:^(id x) { // 只有signalOfsignals的signal發出信號纔會調用,由於內部訂閱了bindBlock中返回的信號,也就是flattenMap返回的信號。 // 也就是flattenMap返回的信號發出內容,纔會調用。 NSLog(@"signalOfsignals:%@",x); }]; // 信號的信號發送信號 [signalOfsignals sendNext:signal]; // 信號發送內容 [signal sendNext:@"hi"]; 

組合

組合就是將多個信號按照某種規則進行拼接,合成新的信號。

concat
  • 做用

    順序拼接信號,當多個信號發出的時候,有順序的接收信號。

  • 底層實現

    1. 當拼接信號被訂閱,就會調用拼接信號的didSubscribe
    2. didSubscribe中,會先訂閱第一個源信號(signalA)
    3. 會執行第一個源信號(signalA)的didSubscribe
    4. 第一個源信號(signalA)didSubscribe中發送值,就會調用第一個源信號(signalA)訂閱者的nextBlock,經過拼接信號的訂閱者把值發送出來.
    5. 第一個源信號(signalA)didSubscribe中發送完成,就會調用第一個源信號(signalA)訂閱者的completedBlock,訂閱第二個源信號(signalB)這時候才激活(signalB)。
    6. 訂閱第二個源信號(signalB),執行第二個源信號(signalB)的didSubscribe
    7. 第二個源信號(signalA)didSubscribe中發送值,就會經過拼接信號的訂閱者把值發送出來.
  • 使用步驟

    1. 使用concat:拼接信號
    2. 訂閱拼接信號,內部會自動按拼接順序訂閱信號
  • 使用

    拼接信號 signalAsignalBsignalC

    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"Hello"]; [subscriber sendCompleted]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"World"]; [subscriber sendCompleted]; return nil; }]; RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"!"]; [subscriber sendCompleted]; return nil; }]; // 拼接 A B, 把signalA拼接到signalB後,signalA發送完成,signalB纔會被激活。 RACSignal *concatSignalAB = [signalA concat:signalB]; // A B + C RACSignal *concatSignalABC = [concatSignalAB concat:signalC]; // 訂閱拼接的信號, 內部會按順序訂閱 A->B->C // 注意:第一個信號必須發送完成,第二個信號纔會被激活... [concatSignalABC subscribeNext:^(id x) { NSLog(@"%@", x); }]; 
then
  • 做用

    用於鏈接兩個信號,當第一個信號完成,纔會鏈接then返回的信號。

  • 底層實現

    1. 先過濾掉以前的信號發出的值
    2. 使用concat鏈接then返回的信號
  • 使用

    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@1]; [subscriber sendCompleted]; return nil; }] then:^RACSignal *{ return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@2]; return nil; }]; }] subscribeNext:^(id x) { // 只能接收到第二個信號的值,也就是then返回信號的值 NSLog(@"%@", x); }]; /// 輸出:2 
  • 注意

    注意使用then,以前信號的值會被忽略掉.

merge
  • 做用

    合併信號,任何一個信號發送數據,都能監聽到.

  • 底層實現

    1. 合併信號被訂閱的時候,就會遍歷全部信號,而且發出這些信號。
    2. 每發出一個信號,這個信號就會被訂閱
    3. 也就是合併信號一被訂閱,就會訂閱裏面全部的信號。
    4. 只要有一個信號被髮出就會被監聽。
  • 使用

    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"A"]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B"]; return nil; }]; // 合併信號, 任何一個信號發送數據,都能監聽到 RACSignal *mergeSianl = [signalA merge:signalB]; [mergeSianl subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 輸出 2017-01-03 13:29:08.013 ReactiveCocoa進階[3627:718315] A 2017-01-03 13:29:08.014 ReactiveCocoa進階[3627:718315] B 
##### zip - **做用** 把兩個信號壓縮成一個信號,只有當兩個信號 **同時** 發出信號內容時,而且把兩個信號的內容合併成一個元組,纔會觸發壓縮流的next事件。 - **底層實現** 1. 定義壓縮信號,內部就會自動訂閱signalA,signalB 2. 每當signalA或者signalB發出信號,就會判斷signalA,signalB有沒有發出個信號,有就會把每一個信號 第一次 發出的值包裝成元組發出 - **使用** ``` RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"A1"]; [subscriber sendNext:@"A2"]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B1"]; [subscriber sendNext:@"B2"]; [subscriber sendNext:@"B3"]; return nil; }]; RACSignal *zipSignal = [signalA zipWith:signalB]; [zipSignal subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 輸出 2017-01-03 13:48:09.234 ReactiveCocoa進階[3997:789720] zipWith: <RACTuple: 0x600000004df0> ( A1, B1 ) 2017-01-03 13:48:09.234 ReactiveCocoa進階[3997:789720] zipWith: <RACTuple: 0x608000003410> ( A2, B2 ) ``` ##### combineLatest - **做用** 將多個信號合併起來,而且拿到各個信號最後一個值,必須每一個合併的signal至少都有過一次sendNext,纔會觸發合併的信號。 - **底層實現** 1. 當組合信號被訂閱,內部會自動訂閱signalA,signalB,必須兩個信號都發出內容,纔會被觸發。 2. 而且把兩個信號的 最後一次 發送的值組合成元組發出。 - **使用** ``` RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"A1"]; [subscriber sendNext:@"A2"]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B1"]; [subscriber sendNext:@"B2"]; [subscriber sendNext:@"B3"]; return nil; }]; RACSignal *combineSianal = [signalA combineLatestWith:signalB]; [combineSianal subscribeNext:^(id x) { NSLog(@"combineLatest:%@", x); }]; // 輸出 2017-01-03 13:48:09.235 ReactiveCocoa進階[3997:789720] combineLatest:<RACTuple: 0x60800000e150> ( A2, B1 ) 2017-01-03 13:48:09.235 ReactiveCocoa進階[3997:789720] combineLatest:<RACTuple: 0x600000004db0> ( A2, B2 ) 2017-01-03 13:48:09.236 ReactiveCocoa進階[3997:789720] combineLatest:<RACTuple: 0x60800000e180> ( A2, B3 ) ``` - **注意** **combineLatest**與**zip**用法類似,必須每一個合併的signal至少都有過一次sendNext,纔會觸發合併的信號。 區別看下圖: ![](https://ww2.sinaimg.cn/large/006y8lVagw1fbdf6cyez6j30id0kkabf.jpg) ##### reduce - **做用** 把信號發出元組的值聚合成一個值 - **底層實現** 1. 訂閱聚合信號, 2. 每次有內容發出,就會執行reduceblcok,把信號內容轉換成reduceblcok返回的值。 - **使用** 常見的用法,(先組合在聚合)`combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock` reduce中的block簡介: reduceblcok中的參數,有多少信號組合,reduceblcok就有多少參數,每一個參數就是以前信號發出的內容 reduceblcok的返回值:聚合信號以後的內容。 

RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

[subscriber sendNext:@"A1"]; [subscriber sendNext:@"A2"]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B1"]; [subscriber sendNext:@"B2"]; [subscriber sendNext:@"B3"]; return nil; }]; RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA, signalB] reduce:^id(NSString *str1, NSString *str2){ return [NSString stringWithFormat:@"%@ %@", str1, str2]; }]; [reduceSignal subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 輸出 2017-01-03 15:42:41.803 ReactiveCocoa進階[4248:1264674] A2 B1 2017-01-03 15:42:41.803 ReactiveCocoa進階[4248:1264674] A2 B2 2017-01-03 15:42:41.803 ReactiveCocoa進階[4248:1264674] A2 B3 ``` 

過濾

過濾就是過濾信號中的 特定值 ,或者過濾指定 發送次數 的信號。

filter
  • 做用

    過濾信號,使用它能夠獲取知足條件的信號.

    block的返回值是Bool值,返回NO則過濾該信號

  • 使用

    // 過濾: // 每次信號發出,會先執行過濾條件判斷. [[_textField.rac_textSignal filter:^BOOL(NSString *value) { NSLog(@"原信號: %@", value); // 過濾 長度 <= 3 的信號 return value.length > 3; }] subscribeNext:^(id x) { NSLog(@"長度大於3的信號:%@", x); }]; // 在_textField中輸出12345 // 輸出 2017-01-03 16:36:54.938 ReactiveCocoa進階[4714:1552910] 原信號: 1 2017-01-03 16:36:55.383 ReactiveCocoa進階[4714:1552910] 原信號: 12 2017-01-03 16:36:55.706 ReactiveCocoa進階[4714:1552910] 原信號: 123 2017-01-03 16:36:56.842 ReactiveCocoa進階[4714:1552910] 原信號: 1234 2017-01-03 16:36:56.842 ReactiveCocoa進階[4714:1552910] 長度大於3的信號:1234 2017-01-03 16:36:58.350 ReactiveCocoa進階[4714:1552910] 原信號: 12345 2017-01-03 16:36:58.351 ReactiveCocoa進階[4714:1552910] 長度大於3的信號:12345 
ignore
  • 做用

    忽略某些信號.

  • 使用

  • 做用

    忽略某些值的信號.

    底層調用了 filter 與 過濾值進行比較,若相等返回則 NO

  • 使用

     

// 內部調用filter過濾,忽略掉字符爲 @「1」的值
[[_textField.rac_textSignal ignore:@"1"] subscribeNext:^(id x) {

NSLog(@"%@",x); 

}];

##### distinctUntilChanged - **做用** 當上一次的值和當前的值有明顯的變化就會發出信號,不然會被忽略掉。 - **使用** ``` [[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) { NSLog(@"%@",x); }]; ``` ##### skip - **做用** 跳過 **第N次** 的發送的信號. - **使用** ``` // 表示輸入第一次,不會被監聽到,跳過第一次發出的信號 [[_textField.rac_textSignal skip:1] subscribeNext:^(id x) { NSLog(@"%@",x); }]; ``` ##### take - **做用** 取 **前N次** 的發送的信號. - **使用** ``` RACSubject *subject = [RACSubject subject] ; // 取 前兩次 發送的信號 [[subject take:2] subscribeNext:^(id x) { NSLog(@"%@", x); }]; [subject sendNext:@1]; [subject sendNext:@2]; [subject sendNext:@3]; // 輸出 2017-01-03 17:35:54.566 ReactiveCocoa進階[4969:1677908] 1 2017-01-03 17:35:54.567 ReactiveCocoa進階[4969:1677908] 2 ``` ##### takeLast - **做用** 取 **最後N次** 的發送的信號 前提條件,訂閱者必須調用完成 `sendCompleted`,由於只有完成,就知道總共有多少信號. - **使用** ``` RACSubject *subject = [RACSubject subject] ; // 取 後兩次 發送的信號 [[subject takeLast:2] subscribeNext:^(id x) { NSLog(@"%@", x); }]; [subject sendNext:@1]; [subject sendNext:@2]; [subject sendNext:@3]; // 必須 跳用完成 [subject sendCompleted]; ``` ##### takeUntil - **做用** 獲取信號直到某個信號執行完成 - **使用** ``` // 監聽文本框的改變直到當前對象被銷燬 [_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal]; ``` ##### switchToLatest - **做用** 用於signalOfSignals(信號的信號),有時候信號也會發出信號,會在`signalOfSignals`中,獲取`signalOfSignals`發送的最新信號。 - **注意** switchToLatest:只能用於信號中的信號 - **使用** ``` RACSubject *signalOfSignals = [RACSubject subject]; RACSubject *signal = [RACSubject subject]; // 獲取信號中信號最近發出信號,訂閱最近發出的信號。 [signalOfSignals.switchToLatest subscribeNext:^(id x) { NSLog(@"%@", x); }]; [signalOfSignals sendNext:signal]; [signal sendNext:@1]; ``` #### 秩序 秩序包括 `doNext` 和 `doCompleted` 這兩個方法,主要是在 執行`sendNext` 或者 `sendCompleted`以前,先執行這些方法中Block。 ##### doNext 執行`sendNext`以前,會先執行這個`doNext`的 Block ##### doCompleted 執行`sendCompleted`以前,會先執行這`doCompleted`的`Block` 

[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

[subscriber sendNext:@"hi"]; [subscriber sendCompleted]; return nil; 

}] doNext:^(id x) {

// 執行 [subscriber sendNext:@"hi"] 以前會調用這個 Block NSLog(@"doNext"); 

}] doCompleted:^{

// 執行 [subscriber sendCompleted] 以前會調用這 Block NSLog(@"doCompleted"); 

}] subscribeNext:^(id x) {

NSLog(@"%@", x); 

}];

#### 線程 **ReactiveCocoa** 中的線程操做 包括 `deliverOn` 和 `subscribeOn`這兩種,將 *傳遞的內容* 或 建立信號時 *block中的代碼* 切換到指定的線程中執行。 ##### deliverOn - **做用** 內容傳遞切換到指定線程中,反作用在原來線程中,把在建立信號時block中的代碼稱之爲反作用。 - **使用** ``` // 在子線程中執行 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"%@", [NSThread currentThread]); [subscriber sendNext:@123]; [subscriber sendCompleted]; return nil; }] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) { NSLog(@"%@", x); NSLog(@"%@", [NSThread currentThread]); }]; }); // 輸出 2017-01-04 10:35:55.415 ReactiveCocoa進階[1183:224535] <NSThread: 0x608000270f00>{number = 3, name = (null)} 2017-01-04 10:35:55.415 ReactiveCocoa進階[1183:224482] 123 2017-01-04 10:35:55.415 ReactiveCocoa進階[1183:224482] <NSThread: 0x600000079bc0>{number = 1, name = main} ``` 能夠看到`反作用`在 *子線程* 中執行,而 `傳遞的內容` 在 *主線程* 中接收 ##### subscribeOn - **做用** **subscribeOn**則是將 `內容傳遞` 和 `反作用` 都會切換到指定線程中 - **使用** ``` dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"%@", [NSThread currentThread]); [subscriber sendNext:@123]; [subscriber sendCompleted]; return nil; }] subscribeOn:[RACScheduler mainThreadScheduler]] //傳遞的內容到主線程中 subscribeNext:^(id x) { NSLog(@"%@", x); NSLog(@"%@", [NSThread currentThread]); }]; }); // 2017-01-04 10:44:47.558 ReactiveCocoa進階[1243:275126] <NSThread: 0x608000077640>{number = 1, name = main} 2017-01-04 10:44:47.558 ReactiveCocoa進階[1243:275126] 123 2017-01-04 10:44:47.558 ReactiveCocoa進階[1243:275126] <NSThread: 0x608000077640>{number = 1, name = main} ``` `內容傳遞` 和 `反作用` 都切換到了 *主線程* 執行 #### 時間 時間操做就會設置信號超時,定時和延時。 ##### interval 定時 - **做用** 定時:每隔一段時間發出信號 ``` // 每隔1秒發送信號,指定當前線程執行 [[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) { NSLog(@"定時:%@", x); }]; // 輸出 2017-01-04 13:48:55.196 ReactiveCocoa進階[1980:492724] 定時:2017-01-04 05:48:55 +0000 2017-01-04 13:48:56.195 ReactiveCocoa進階[1980:492724] 定時:2017-01-04 05:48:56 +0000 2017-01-04 13:48:57.196 ReactiveCocoa進階[1980:492724] 定時:2017-01-04 05:48:57 +0000 ``` ##### timeout 超時 - **做用** 超時,可讓一個信號在必定的時間後,自動報錯。 ``` RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { // 不發送信號,模擬超時狀態 // [subscriber sendNext:@"hello"]; //[subscriber sendCompleted]; return nil; }] timeout:1 onScheduler:[RACScheduler currentScheduler]];// 設置1秒超時 [signal subscribeNext:^(id x) { NSLog(@"%@", x); } error:^(NSError *error) { NSLog(@"%@", error); }]; // 執行代碼 1秒後 輸出: 2017-01-04 13:48:55.195 ReactiveCocoa進階[1980:492724] Error Domain=RACSignalErrorDomain Code=1 "(null)" ``` ##### delay 延時 - **做用** 延時,延遲一段時間後發送信號 ``` RACSignal *signal2 = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"延遲輸出"]; return nil; }] delay:2] subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 執行代碼 2秒後 輸出 2017-01-04 13:55:23.751 ReactiveCocoa進階[2030:525038] 延遲輸出 ``` #### 重複 ##### retry - **做用** 重試:只要 發送錯誤 `sendError:`,就會 從新執行 建立信號的Block 直到成功 ``` __block int i = 0; [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { if (i == 5) { [subscriber sendNext:@"Hello"]; } else { // 發送錯誤 NSLog(@"收到錯誤:%d", i); [subscriber sendError:nil]; } i++; return nil; }] retry] subscribeNext:^(id x) { NSLog(@"%@", x); } error:^(NSError *error) { NSLog(@"%@", error); }]; // 輸出 2017-01-04 14:36:51.594 ReactiveCocoa進階[2443:667226] 收到錯誤信息:0 2017-01-04 14:36:51.595 ReactiveCocoa進階[2443:667226] 收到錯誤信息:1 2017-01-04 14:36:51.595 ReactiveCocoa進階[2443:667226] 收到錯誤信息:2 2017-01-04 14:36:51.596 ReactiveCocoa進階[2443:667226] 收到錯誤信息:3 2017-01-04 14:36:51.596 ReactiveCocoa進階[2443:667226] 收到錯誤信息:4 2017-01-04 14:36:51.596 ReactiveCocoa進階[2443:667226] Hello ``` ##### replay - **做用** 重放:當一個信號被屢次訂閱,反覆播放內容 ``` RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; return nil; }] replay]; [signal subscribeNext:^(id x) { NSLog(@"%@", x); }]; [signal subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 輸出 2017-01-04 14:51:01.934 ReactiveCocoa進階[2544:706740] 1 2017-01-04 14:51:01.934 ReactiveCocoa進階[2544:706740] 2 2017-01-04 14:51:01.934 ReactiveCocoa進階[2544:706740] 1 2017-01-04 14:51:01.935 ReactiveCocoa進階[2544:706740] 2 ``` ##### throttle - **做用** 節流:當某個信號發送比較頻繁時,可使用節流,在某一段時間不發送信號內容,過了一段時間獲取信號的最新內容發出。 ``` RACSubject *subject = [RACSubject subject]; // 節流1秒,1秒後接收最後一個發送的信號 [[subject throttle:1] subscribeNext:^(id x) { NSLog(@"%@", x); }]; [subject sendNext:@1]; [subject sendNext:@2]; [subject sendNext:@3]; // 輸出 2017-01-04 15:02:37.543 ReactiveCocoa進階[2731:758193] 3 ``` ## MVVM架構思想 --- 程序爲何要有架構?便於程序開發與維護. #### 常見的架構 - **MVC** M:模型 V:視圖 C:控制器 - **MVVM** M:模型 V:視圖+控制器 VM:視圖模型 - **MVCS** M:模型 V:視圖 C:控制器 C:服務類 - [**VIPER**](http://www.cocoachina.com/ios/20140703/9016.html) V:視圖 I:交互器 P:展現器 E:實體 R:路由 #### MVVM介紹 - 模型(M):保存視圖數據。 - 視圖+控制器(V):展現內容 + 如何展現 - 視圖模型(VM):處理展現的業務邏輯,包括按鈕的點擊,數據的請求和解析等等。 ## 實戰一:登陸界面 #### 需求 1. 監聽兩個文本框的內容 2. 有內容登陸按鍵才容許按鈕點擊 3. 返回登陸結果 #### 分析 1. 界面的全部業務邏輯都交給控制器作處理 2. 在MVVM架構中把控制器的業務所有搬去VM模型,也就是每一個控制器對應一個VM模型. #### 步驟 1. 建立LoginViewModel類,處理登陸界面業務邏輯. 2. 這個類裏面應該保存着帳號的信息,建立一個帳號Account模型 3. LoginViewModel應該保存着帳號信息Account模型。 4. 須要時刻監聽Account模型中的帳號和密碼的改變,怎麼監聽? 5. 在非RAC開發中,都是習慣賦值,在RAC開發中,須要改變開發思惟,由賦值轉變爲綁定,能夠在一開始初始化的時候,就給Account模型中的屬性綁定,並不須要重寫set方法。 6. 每次Account模型的值改變,就須要判斷按鈕可否點擊,在VM模型中作處理,給外界提供一個可否點擊按鈕的信號. 7. 這個登陸信號須要判斷Account中帳號和密碼是否有值,用KVO監聽這兩個值的改變,把他們聚合成登陸信號. 8. 監聽按鈕的點擊,由VM處理,應該給VM聲明一個RACCommand,專門處理登陸業務邏輯. 9. 執行命令,把數據包裝成信號傳遞出去 10. 監聽命令中信號的數據傳遞 11. 監聽命令的執行時刻 #### 運行效果 ![登陸界面](https://ww3.sinaimg.cn/large/006y8lVagw1fbgvoh8yu6j30bj0l43yz.jpg) #### 代碼 `MyViewController.m` 

import "MyViewController.h"

import "LoginViewModel.h"

@interface MyViewController ()

@property (nonatomic, strong) LoginViewModel *loginViewModel;

@property (weak, nonatomic) IBOutlet UITextField *accountField;

@property (weak, nonatomic) IBOutlet UITextField *pwdField;

@property (weak, nonatomic) IBOutlet UIButton *loginBtn;

@end

@implementation MyViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    [self bindModel];

}

  • (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    }

// 視圖模型綁定

  • (void)bindModel {

    // 給模型的屬性綁定信號
    //
    RAC(self.loginViewModel.account, account) = _accountField.rac_textSignal;
    RAC(self.loginViewModel.account, pwd) = _pwdField.rac_textSignal;

    RAC(self.loginBtn, enabled) = self.loginViewModel.enableLoginSignal;

    // 監聽登陸點擊
    [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {

    [self.loginViewModel.LoginCommand execute:nil]; 

    }];

}

  • (IBAction)btnTap:(id)sender {

}

pragma mark - lazyLoad

  • (LoginViewModel *)loginViewModel {

    if (nil == _loginViewModel) {
    _loginViewModel = [[LoginViewModel alloc] init];
    }

    return _loginViewModel;
    }

`LoginViewModel.h` 

import <UIKit/UIKit.h>

@interface Account : NSObject

@property (nonatomic, strong) NSString *account;
@property (nonatomic, strong) NSString *pwd;

@end

@interface LoginViewModel : UIViewController

@property (nonatomic, strong) Account *account;

// 是否容許登陸的信號
@property (nonatomic, strong, readonly) RACSignal *enableLoginSignal;

@property (nonatomic, strong, readonly) RACCommand *LoginCommand;

@end

`LoginViewModel.m` 

import "LoginViewModel.h"

@implementation Account

@end

@interface LoginViewModel ()

@end

@implementation LoginViewModel

  • (instancetype)init {

    if (self = [super init]) {
    [self initialBind];
    }
    return self;
    }

  • (void)initialBind {

    // 監聽帳號屬性改變, 把他們合成一個信號
    _enableLoginSignal = [RACSubject combineLatest:@[RACObserve(self.account, account), RACObserve(self.account, pwd)] reduce:^id(NSString *accout, NSString *pwd){

    return @(accout.length && pwd.length); 

    }];

    // 處理業務邏輯
    _LoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

    NSLog(@"點擊了登陸"); return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { // 模仿網絡延遲 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 返回登陸成功 發送成功信號 [subscriber sendNext:@"登陸成功"]; }); return nil; }]; 

    }];

// 監聽登陸產生的數據 [_LoginCommand.executionSignals.switchToLatest subscribeNext:^(id x) { if ([x isEqualToString:@"登陸成功"]) { NSLog(@"登陸成功"); } }]; [[_LoginCommand.executing skip:1] subscribeNext:^(id x) { if ([x isEqualToNumber:@(YES)]) { NSLog(@"正在登錄..."); } else { // 登陸成功 NSLog(@"登錄成功"); } }]; 

}

pragma mark - lazyLoad

  • (Account *)account
    {
    if (_account == nil) {
    _account = [[Account alloc] init];
    }
    return _account;
    }

  • (void)viewDidLoad {
    [super viewDidLoad];

}

@end

## 實戰二:網絡請求數據 #### 需求 1. 請求一段網絡數據,將請求到的數據在`tableView`上展現 2. 該數據爲豆瓣圖書的搜索返回結果,URL:url:https://api.douban.com/v2/book/search?q=悟空傳 #### 分析 1. 界面的全部業務邏輯都交給**控制器**作處理 2. 網絡請求交給**MV**模型處理 #### 步驟 1. 控制器提供一個視圖模型(requesViewModel),處理界面的業務邏輯 2. VM提供一個命令,處理請求業務邏輯 3. 在建立命令的block中,會把請求包裝成一個信號,等請求成功的時候,就會把數據傳遞出去。 4. 請求數據成功,應該把字典轉換成模型,保存到視圖模型中,控制器想用就直接從視圖模型中獲取。 ####其餘 網絡請求與圖片緩存用到了[AFNetworking](https://github.com/AFNetworking/AFNetworking) 和 [SDWebImage](https://github.com/rs/SDWebImage),自行在Pods中導入。 

platform :ios, '8.0'

target 'ReactiveCocoa進階' do

use_frameworks!
pod 'ReactiveCocoa', '~> 2.5'
pod 'AFNetworking'
pod 'SDWebImage'
end

#### 運行效果 ![](https://ww3.sinaimg.cn/large/006y8lVagw1fbgw1xnz74j30bj0l4408.jpg) #### 代碼 `SearchViewController.m` 

import "SearchViewController.h"

import "RequestViewModel.h"

@interface SearchViewController ()<UITableViewDataSource>

@property (nonatomic, strong) UITableView *tableView;

@property (nonatomic, strong) RequestViewModel *requesViewModel;

@end

@implementation SearchViewController

  • (RequestViewModel *)requesViewModel
    {
    if (_requesViewModel == nil) {
    _requesViewModel = [[RequestViewModel alloc] init];
    }
    return _requesViewModel;
    }

  • (void)viewDidLoad {
    [super viewDidLoad];

self.tableView = [[UITableView alloc] initWithFrame:self.view.frame]; self.tableView.dataSource = self; [self.view addSubview:self.tableView]; // RACSignal *requesSiganl = [self.requesViewModel.reuqesCommand execute:nil]; [requesSiganl subscribeNext:^(NSArray *x) { self.requesViewModel.models = x; [self.tableView reloadData]; }]; 

}

  • (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    }

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    return self.requesViewModel.models.count;
    }

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    static NSString *ID = @"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) {

    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; 

    }

    Book *book = self.requesViewModel.models[indexPath.row];
    cell.detailTextLabel.text = book.subtitle;
    cell.textLabel.text = book.title;

    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:book.image] placeholderImage:[UIImage imageNamed:@"cellImage"]];

return cell; 

}
@end

`RequestViewModel.h` 

import <Foundation/Foundation.h>

@interface Book : NSObject

@property (nonatomic, copy) NSString *subtitle;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *image;

@end

@interface RequestViewModel : NSObject

// 請求命令
@property (nonatomic, strong, readonly) RACCommand *reuqesCommand;

//模型數組
@property (nonatomic, strong) NSArray *models;

@end

`RequestViewModel.m` 

import "RequestViewModel.h"

@implementation Book

  • (instancetype)initWithValue:(NSDictionary *)value {

    if (self = [super init]) {

    self.title = value[@"title"]; self.subtitle = value[@"subtitle"]; self.image = value[@"image"]; 

    }
    return self;
    }

  • (Book *)bookWithDict:(NSDictionary *)value {

    return [[self alloc] initWithValue:value];
    }

@end

@implementation RequestViewModel

  • (instancetype)init
    {
    if (self = [super init]) {

    [self initialBind]; 

    }
    return self;
    }

  • (void)initialBind
    {
    _reuqesCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

    RACSignal *requestSiganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; parameters[@"q"] = @"悟空傳"; // [[AFHTTPSessionManager manager] GET:@"https://api.douban.com/v2/book/search" parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) { NSLog(@"downloadProgress: %@", downloadProgress); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { // 數據請求成功就講數據發送出去 NSLog(@"responseObject:%@", responseObject); [subscriber sendNext:responseObject]; [subscriber sendCompleted]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"error: %@", error); }]; return nil; }]; // 在返回數據信號時,把數據中的字典映射成模型信號,傳遞出去 return [requestSiganl map:^id(NSDictionary *value) { NSMutableArray *dictArr = value[@"books"]; NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) { return [Book bookWithDict:value]; }] array]; return modelArr; }]; 

    }];
    }

@end

做者:BYQiu 連接:https://www.jianshu.com/p/e0b0afd0519f 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索