ReactiveCocoa應用篇(二)

上一篇介紹了ReactiveCocoa的常用類,已經基本滿足項目中的簡單應用要求,但是針對複雜的功能還需要其它的類來協同處理。ReactiveCocoa提供了強大的流程處理功能來解決複雜的問題,包括事件點擊、代理、通知、事件同步和異步等等,可以簡化代碼體量,實現 高聚合、低耦合 的編程思想。下面ReactiveCocoa的更強大的功能:

一. RACTuple、RACSequence

RACTuple: 元組類,類似NSArray,在解構對象中經常使用

RACSequence: 集合類,使用它來快速遍歷數組和字典

    // 1.遍歷數組
    NSArray *numbers = @[@1,@2,@3,@4];

    // 這裏其實是三步
    // 第一步: 把數組轉換成集合RACSequence numbers.rac_sequence
    // 第二步: 把集合RACSequence轉換RACSignal信號類,numbers.rac_sequence.signal
    // 第三步: 訂閱信號,激活信號,會自動把集合中的所有值,遍歷出來。
    [numbers.rac_sequence.signal subscribeNext:^(id x) {

        NSLog(@"%@",x);
    }];
    // 2.遍歷字典,遍歷出來的鍵值對會包裝成RACTuple(元組對象)
    NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {

        // 解包元組,會把元組的值,按順序給參數裏面的變量賦值
        RACTupleUnpack(NSString *key,NSString *value) = x;

        // 相當於以下寫法
        // NSString *key = x[0];
        // NSString *value = x[1];

        NSLog(@"%@ %@",key,value);

    }];

二. RACScheduler、RACUnit、RACEvent

  • RACScheduler: RAC中的隊列,用GCD封裝的

  • RACUnit: 表⽰stream不包含有意義的值,也就是看到這個,可以直接理解爲nil

  • RACEvent: 把數據包裝成信號事件(signal event)。它主要通過RACSignal的-materialize來使用,然並卵

三. 事件監聽

  • 代替代理: rac_signalForSelector

之前需要遵守代理協議、賦值delegate、實現代理方法等都不需要,只用rac_signalForSelector就可以實現

    [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"點擊紅色按鈕");
    }];
  • 代替KVO:rac_valuesAndChangesForKeyPath
    [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
  • 監聽事件:rac_signalForControlEvents
    [[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {

        NSLog(@"按鈕被點擊了");
    }];
  • 代替通知:rac_addObserverForName
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"鍵盤彈出");
    }];
  • 監聽文本框文字改變:rac_textSignal
    [_textField.rac_textSignal subscribeNext:^(id x) {
        NSLog(@"文字改變了%@",x);
    }];
  • 同步信號:rac_liftSelector:withSignalsFromArray:Signals
    - (void)viewDidLoad
    {
        RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        // 發送請求1
        [subscriber sendNext:@"發送請求1"];
            return nil;
        }];

        RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id subscriber) {
            // 發送請求2
            [subscriber sendNext:@"發送請求2"];
            return nil;
        }];

        // 使用注意:幾個信號,參數一的方法就幾個參數,每個參數對應信號發出的數據。
        [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
    }

    // 更新UI
    - (void)updateUIWithR1:(id)data r2:(id)data1
    {
        NSLog(@"更新UI%@ %@",data,data1);
    }

四. 常見宏

  • RAC: 於給某個對象的某個屬性綁定
    // 只要文本框文字改變,就會修改label的文字
    RAC(self.labelView,text) = _textField.rac_textSignal;
  • RACObserve: 監聽某個對象的某個屬性,返回的是信號
    [RACObserve(self.view, center) subscribeNext:^(id x) {

        NSLog(@"%@",x);
    }];
  • @weakify(Obj)和@strongify(Obj):處理閉包強引用

  • RACTupleRACTupleUnpack: 元組的構造與解構

    // 把參數中的數據包裝成元組
    RACTuple *tuple = RACTuplePack(@10,@20);

    // 把參數中的數據包裝成元組
    RACTuple *tuple = RACTuplePack(@"xmg",@20);

    // 解包元組,會把元組的值,按順序給參數裏面的變量賦值
    // name = @"xmg" age = @20
    RACTupleUnpack(NSString *name,NSNumber *age) = tuple;

五. 常用操作方法之映射

1. flattenMap

flattenMap作用:把源信號的內容映射成一個新的信號,信號可以是任意類型

  • 使用步驟
    1.傳入一個block,block類型是返回值RACStream,參數value
    2.參數value就是源信號的內容,拿到源信號的內容做處理
    3.包裝成RACReturnSignal信號,返回出去。
  • 底層實現
    0.flattenMap內部調用bind方法實現的,flattenMap中block的返回值,會作爲bindbindBlock的返回值。
    1.當訂閱綁定信號,就會生成bindBlock。
    2.當源信號發送內容,就會調用bindBlock(value, *stop)
    3.調用bindBlock,內部就會調用flattenMap的block,flattenMap的block作用:就是把處理好的數據包裝成信號。
    4.返回的信號最終會作爲bindBlock中的返回信號,當做bindBlock的返回信號。
    5.訂閱bindBlock的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。
  • 代碼實現
    // 創建信號
    RACSubject *subject = [RACSubject subject];

    // 綁定信號
    RACSignal *bindSignal = [subject flattenMap:^RACStream *(id value) {
        // block:只要源信號發送內容就會調用
        // value:就是源信號發送內容

        value = [NSString stringWithFormat:@"%@",value];
        NSLog(@"%@",value);
        // block:只要源信號發送內容就會調用
        // value:就是源信號發送內容
        return [RACReturnSignal return:value];
    }];

    // flattenMap中返回的是什麼信號,訂閱的就是什麼信號
    // 訂閱信號
    [bindSignal subscribeNext:^(id x) {

        NSLog(@"----%@",x);
    }];

    // 發送數據
    [subject sendNext:@"abcd"];

2. Map

把源信號的值映射成一個新的值,返回一個對象

  • 使用步驟
    1.傳入一個block,類型是返回對象,參數是value
    2.value就是源信號的內容,直接拿到源信號的內容做處理
    3.把處理好的內容,直接返回就好了,不用包裝成信號,返回的值,就是映射的值。
  • 底層實現
    1.Map底層其實是調用flatternMap,Mapblock中的返回的值會作爲flatternMap中block中的值。
    2.當訂閱綁定信號,就會生成bindBlock。
    3.當源信號發送內容,就會調用bindBlock(value, *stop)
    4.調用bindBlock,內部就會調用flattenMap的block
    5.flattenMap的block內部會調用Map中的block,把Map中的block返回的內容包裝成返回的信號。
    6.返回的信號最終會作爲bindBlock中的返回信號,當做bindBlock的返回信號。
    7.訂閱bindBlock的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送
  • 代碼實現
    RACSubject *subject = [RACSubject subject];

    RACSignal *bindSignal = [subject map:^id(id value) {
        // 返回的類型,就是你需要映射的值
        return [NSString stringWithFormat:@"%@",value];
    }];

    [bindSignal subscribeNext:^(id x) {

        NSLog(@"%@",x);
    }];

    [subject sendNext:@"123"];
    [subject sendNext:@"456"]

六. 操作方法之組合

1. 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中發送值,就會通過拼接信號的訂閱者把值發送出來
  • 代碼實現
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        // 發送信號
        [subscriber sendNext:@"123"];

        [subscriber sendCompleted];
        return nil;
    }];

    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        // 發送信號
        [subscriber sendNext:@"abc"];

        return nil;
    }];

    // concat:按順序去連接
    // 注意:concat,第一個信號必須要調用sendCompleted
    // 創建組合信號

    RACSignal *concatSignal = [signalA concat:signalB];

    // 訂閱組合信號

    [concatSignal subscribeNext:^(id x) {

        // 既能拿到A信號的值,又能拿到B信號的值
        NSLog(@"%@",x);
    }];

2. then

用於連接兩個信號,當第一個信號完成,纔會連接then返回的信號。注意使用then,之前信號的值會被忽略掉

  • 底層實現
    1. 先過濾掉之前的信號發出的值。
    2. 使用concat連接then返回的信號
  • 代碼實現
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        // 發送信號
        [subscriber sendNext:@"abc"];

        [subscriber sendCompleted];
        return nil;
    }];

    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        // 發送信號
        [subscriber sendNext:@"123"];

        return nil;
    }];

    // 創建組合信號
    // then:忽悠掉第一個信號所有值
    RACSignal *thenSignal = [signalA then:^RACSignal *{
        // 返回信號就是需要組合的信號
        return signalB;
    }];

    // 訂閱信號

    [thenSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

3. merge

把多個信號合併爲一個信號,任何一個信號有新值的時候就會調用

  • 底層實現
    1.合併信號被訂閱的時候,就會遍歷所有信號,並且發出這些信號。
    2.每發出一個信號,這個信號就會被訂閱
    3.也就是合併信號一被訂閱,就會訂閱裏面所有的信號。
    4.只要有一個信號被髮出就會被監聽。
  • 代碼實現
    // 任意一個信號請求完成都會訂閱到

    RACSubject *signalA = [RACSubject subject];

    RACSubject *signalB = [RACSubject subject];

    // 組合信號
    RACSignal *mergeSignal = [signalA merge:signalB];

    // 訂閱信號

    [mergeSignal subscribeNext:^(id x) {
        // 任意一個信號發送內容都會來這個block
        NSLog(@"%@",x);
    }];

    // 發送數據
    [signalA sendNext:@"abc"];
    [signalB sendNext:@"123"];

4. zipWith

把兩個信號壓縮成一個信號,只有當兩個信號同時發出信號內容時,並且把兩個信號的內容合併成一個元組,纔會觸發壓縮流的next事件

  • 底層實現
    1. 定義壓縮信號,內部就會自動訂閱signalA,signalB
    2. 每當signalA或者signalB發出信號,就會判斷signalA,signalB有沒有發出個信號,有就會把最近發出的信號都包裝成元組發出。
  • 代碼實現
    RACSubject *signalA = [RACSubject subject];

    RACSubject *signalB = [RACSubject subject];

    // 壓縮成一個信號
    // zipWith:當一個界面多個請求的時候,要等所有請求完成才能更新UI
    // zipWith:等所有信號都發送內容的時候纔會調用

    RACSignal *zipSignal = [signalA zipWith:signalB];

    // 訂閱信號
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    // 發送信號
    // 注意: 打印順序是由壓縮的信號順序決定的,不是由發送信號的順序決定的。
    [signalB sendNext:@"123"];
    [signalA sendNext:@"abc"];

5.reduce

用於信號發出的內容是元組,把信號發出元組的值聚合成一個值

  • 底層實現
    訂閱聚合信號,每次有內容發出,就會執行reduceblcok,把信號內容轉換成reduceblcok返回的值。
  • 代碼實現
    // 聚合
    // 常見的用法,(先組合在聚合)。combineLatest:(id)signals reduce:(id (^)())reduceBlock
    // reduce中的block簡介:
    // reduceblcok中的參數,有多少信號組合,reduceblcok就有多少參數,每個參數就是之前信號發出的內容
    // reduceblcok的返回值:聚合信號之後的內容。

    RACSignal *reduceSiganl = [RACSignal combineLatest:@[_accountFiled.rac_textSignal,_pwdField.rac_textSignal] reduce:^id(NSString *account,NSString *pwd){
        // block:只要源信號發送內容就會調用,組合成新一個值
        NSLog(@"%@ %@",account,pwd);
        // 聚合的值就是組合信號的內容

        return @(account.length && pwd.length);
    }];

    // 訂閱組合信號
    // [reduceSiganl subscribeNext:^(id x) {
    //
    // _loginBtn.enabled = [x boolValue];
    // }];

    RAC(_loginBtn,enabled) = reduceSiganl;

6. combine

把兩個信號組合成一個信號,跟zip一樣,沒什麼區別

  • 底層實現
1.當組合信號被訂閱,內部會自動訂閱signalA,signalB,必須兩個信號都發出內容,纔會被觸發。
2.並且把兩個信號組合成元組發出。
  • 代碼實現
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        [subscriber sendNext:@1];

        return nil;
    }];

    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        [subscriber sendNext:@2];

        return nil;
    }];

    // 把兩個信號組合成一個信號,跟zip一樣,沒什麼區別
    RACSignal *combineSignal = [signalA combineLatestWith:signalB];

        [combineSignal subscribeNext:^(id x) {

        NSLog(@"%@",x);
    }];

七、操作方法之過濾

1. filter

使用它可以獲取滿足條件的信號

  • 代碼實現
    // 只有當我們文本框的內容長度大於5,纔想要獲取文本框的內容

    [[_textField.rac_textSignal filter:^BOOL(id value) {
        // value: 源信號的內容
        return [value length] > 5;
        // 返回值,就是過濾條件,只有滿足這個條件,才能能獲取到內容
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

2.ignore

忽略某些值的信號

  • 代碼實現
    // ignore:忽略一些值
    // ignoreValues:忽略所有的值

    // 1.創建信號
    RACSubject *subject = [RACSubject subject];
    // 2.忽略一些
    RACSignal *ignoreSignal = [subject ignoreValues];
    // 3.訂閱信號
    [ignoreSignal subscribeNext:^(id x) {

        NSLog(@"%@",x);
    }];

    // 4.發送數據
    [subject sendNext:@"2"];
    [subject sendNext:@"456"];
    [subject sendNext:@"789"];

3.distinctUntilChanged

當上一次的值和當前的值有明顯的變化就會發出信號,否則會被忽略掉

  • 代碼實現
    // distinctUntilChanged:如果當前的值跟上一個值相同,就不會被訂閱到

    RACSubject *subject = [RACSubject subject];

    [[subject distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@2];
    [subject sendNext:@3];

4.take

從開始一共取N次的信號

  • 代碼實現
    // 1.創建信號
    RACSubject *subject = [RACSubject subject];

    // 2、處理信號,訂閱信號
    [[subject take:2] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    // 3.發送信號
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];

5. takeLast

取最後N次的信號,前提條件,訂閱者必須調用完成,因爲只有完成,就知道總共有多少信號

  • 代碼實現
    // 1.創建信號
    RACSubject *signal = [RACSubject subject];

    // 2、處理信號,訂閱信號
    [[signal takeLast:1] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    // 3.發送信號
    [signal sendNext:@1];
    [signal sendNext:@2];
    [signal sendNext:@3];

    [signal sendCompleted];

6.takeUntil

獲取信號直到執行完這個信號,當發送信號爲空或結束時,後面有發送的信號,也不會執行

  • 代碼實現
    RACSubject *subject = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];

    [[subject takeUntil:signal] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    [subject sendNext:@1];
    [subject sendNext:@"abc"];
    [signal sendError:nil];
    [signal sendNext:@2];
    [signal sendNext:@3];

7.skip

跳過幾個信號,不接受

  • 代碼實現
    // skip;跳躍幾個值
    RACSubject *subject = [RACSubject subject];

    [[subject skip:2] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];

八、操作方法之秩序

  • 執行Next之前,會先執行這個Block

  • 執行sendCompleted之前,會先執行這個Block

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@123];
        [subscriber sendCompleted];
        return nil;
    }];

    // 執行[subscriber sendNext:@1];之前會調用這個Block
    [[[signal doNext:^(id x) {
        NSLog(@"doNext");
    }] doCompleted:^{
        // 執行[subscriber sendCompleted];之前會調用這個Block
        NSLog(@"doCompleted");
    }] subscribeNext:^(id x) {
        NSLog(@"---%@",x);
    }];

九、操作方法之線程

  • deliverOn

內容傳遞切換到制定線程中,副作用在原來線程中,把在創建信號時block中的代碼稱之爲副作用

具體應用

    _executing = [[[[[immediateExecuting
        deliverOn:RACScheduler.mainThreadScheduler]
        // This is useful before the first value arrives on the main thread.
        startWith:@NO]
        distinctUntilChanged]
        replayLast]
        setNameWithFormat:@"%@ -executing", self];
  • subscribeOn

內容傳遞和副作用都會切換到制定線程中

具體應用

    RACMulticastConnection *connection = [[signal
        subscribeOn:RACScheduler.mainThreadScheduler]
        multicast:[RACReplaySubject subject]];

十、操作方法之時間

1.timeout

超時,可以讓一個信號在一定的時間後,自動報錯

    RACSignal *signalA = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:@123];
        });
        return nil;
    }] timeout:1 onScheduler:[RACScheduler currentScheduler]];

    [signalA subscribeNext:^(id x) {

        NSLog(@"%@",x);
    } error:^(NSError *error) {
        // 1秒後會自動調用
        NSLog(@"%@",error);
    }];

2.interval

定時:每隔一段時間發出信號

    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {

        [subscriber sendNext:@123];
        return nil;
        //delay 延遲發送next。
    }] delay:2.0];

    [signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

3. delay

延遲發送next

    [[RACSignal interval:1.0 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
        NSLog(@"123");
    }];

十一、操作方法之重複

1. retry

只要失敗,就會重新執行創建信號中的block,直到成功.

    __block int i = 0;
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        if (i == 5) {
            [subscriber sendNext:@123];
        } else{
            NSLog(@"error");
            [subscriber sendError:nil];
        }

        i++;

        return nil;
    }];

    [[signal retry] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    } error:^(NSError *error) {
        NSLog(@"%@",error);
    }];

2. replay

當一個信號被多次訂閱,反覆播放內容

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {

        [subscriber sendNext:@123];
        [subscriber sendNext:@456];
        return nil;
    }];

    [[signal replay] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    [[signal replay] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

3. throttle

當某個信號發送比較頻繁時,可以使用節流,在某一段時間不發送信號內容,過了一段時間獲取信號的最新內容發出

    RACSubject *signal = [RACSubject subject];
    _signal = signal;
    // 節流,在一定時間(1秒)內,不接收任何信號內容,過了這個時間(1秒)獲取最後發送的信號內容發出。
    // [[signal throttle:2.0] subscribeNext:^(id x) {
    // NSLog(@"%@",x);
    // }];

    [[signal throttle:1.0 valuesPassingTest:^BOOL(id next) {

        return YES;
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        int i = 0;
        [_signal sendNext:@(i)];
        i++;
    }

未完待續…

發佈了49 篇原創文章 · 獲贊 16 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章