Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> #關於RACCommand的思考

#關於RACCommand的思考

編輯:關於Android編程

簡介  在ReactiveCocoa中通過對相關的控件添加了信號的特征,采用category的方法在UIButton中添加其category。可以發現在ReactiveCocoa中有`UIButton+RACComandSupport.h`的頭文件中有
@class RACCommand;

@interface UIButton (RACCommandSupport)

/// Sets the button's command. When the button is clicked, the command is
/// executed with the sender of the event. The button's enabledness is bound
/// to the command's `canExecute`.
@property (nonatomic, strong) RACCommand *rac_command;

@end
通過categoryReactiveCocoa為UIButton添加了一個屬性,也就是一個RACCommand類。通過RACCommand該類可以方便控件完成一些動態性的功能。在本文中我們針對UIButton控件分析了RACComand的相關功能和使用。 第二次測試

首先需要關注的是類RACComand類的幾個關鍵屬性


@property (nonatomic, strong, readonly) RACSignal *executionSignals;

//由於Errors的會在內部被直接捕捉,下文分析initWithSignalBlock方法的時候,會對這一點捕捉錯誤的代碼進行一個詳盡的分析。所以如果想要捕捉錯誤的話,可以采用-execute的方法或者是對block中的信號進行封裝[RACSignal materialize].這個屬性可以定義了一個signal of signals 所以在針對executionSignals的操作時候一般可以采用flatten 或者是flatMap將signal of signals重新映射為signal,但是采用flatMap的方法不能捕捉到Error,和Completed的信號 。

Only executions that begin after subscription will be sent upon this signal. All inner signals will arrive upon the main thread.


@property (nonatomic, strong, readonly) RACSignal *executing;

//該屬性定義了是否還處在執行階段


@property (nonatomic, strong, readonly) RACSignal *enabled;


@property (nonatomic, strong, readonly) RACSignal *errors;

該屬性是把RACComand中的捕捉到的error信息都存在這裡


@property (atomic, assign) BOOL allowsConcurrentExecution;

該屬性判斷可並行性,默認的是NO不可並行,不可並行時候會使得button的enabled的屬性在執行時候變為enabled=NO

##RACCommand源碼分析 關於RACComand的一個最常見的用法形式就是

    _btn.rac_command=
    [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
        //todo
        }];
當然還有另一個常用的函數`- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock`該函數多了一個參數`enabledSignal`可以實現對button的enabled功能的控制。 在RACComand的頭文件當中有以下解釋
/// Initializes a command that is conditionally enabled.
///
/// This is the designated initializer for this class.
///
/// enabledSignal - A signal of BOOLs which indicate whether the command should
///                 be enabled. `enabled` will be based on the latest value sent
///                 from this signal. Before any values are sent, `enabled` will
///                 default to YES. This argument may be nil.
/// signalBlock   - A block which will map each input value (passed to -execute:)
///                 to a signal of work. The returned signal will be multicasted
///                 to a replay subject, sent on `executionSignals`, then
///                 subscribed to synchronously. Neither the block nor the
///                 returned signal may be nil.
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:
                                    (RACSignal * (^)(InputType input))signalBlock;
其中enabledSignal用來判斷Button的enabled什麼時候滿足。該信號可以為nil,默認情況下enabled為YES。 在ReactiveCocoa中針對控件的enabled屬性有很多的應用,例如只有在輸入格式正確的賬號和密碼以後才能使得登錄按鈕變成enable。這樣就可以在viewmodel中定義一個判斷邏輯通過返回內容為BOOL的信號來決定enabled的值。 但是很多采用宏的形式去實現
RAC(btn,enabled)=RACObserve(viewModel,isvalid)
//通過觀察viewModel中的isvalid可以設置Button的enabled是否可用
而其中的signalBlock就是要執行的command,該command只有在enabled為YES的情況下,可以執行。並行與否就是通過allowsConcurrentExecution來控制enabled,而command得執行又去查看enabled的狀態。 注意此處enabled只能綁定一次,綁定多次時候會報錯 下面我們要詳細的分析一下
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(
id input))signalBlock;
這兩個函數,而`- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;`等同於`- (id)initWithEnabled:nil signalBlock:(RACSignal * (^)(id input))signalBlock;`所以重點解釋一下第二個函數。源碼面前了無秘密。這也是IOS的一個痛點,畢竟很多框架都不開源
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {
    NSCParameterAssert(signalBlock != nil);

    self = [super init];
    if (self == nil) return nil;

    _addedExecutionSignalsSubject = [RACSubject new];
    _allowsConcurrentExecutionSubject = [RACSubject new];
    _signalBlock = [signalBlock copy];

    _executionSignals = [[[self.addedExecutionSignalsSubject
        map:^(RACSignal *signal) {
            return [signal catchTo:[RACSignal empty]];
        }]
        deliverOn:RACScheduler.mainThreadScheduler]
        setNameWithFormat:@"%@ -executionSignals", self];

    // `errors` needs to be multicasted so that it picks up all
    // `activeExecutionSignals` that are added.
    //
    // In other words, if someone subscribes to `errors` _after_ an execution
    // has started, it should still receive any error from that execution.
    RACMulticastConnection *errorsConnection = [[[self.addedExecutionSignalsSubject
        flattenMap:^(RACSignal *signal) {
            return [[signal
                ignoreValues]
                catch:^(NSError *error) {
                    return [RACSignal return:error];
                }];
        }]
        deliverOn:RACScheduler.mainThreadScheduler]
        publish];

    _errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
    [errorsConnection connect];

    RACSignal *immediateExecuting = [[[[self.addedExecutionSignalsSubject
        flattenMap:^(RACSignal *signal) {
            return [[[signal
                catchTo:[RACSignal empty]]
                then:^{
                    return [RACSignal return:@-1];
                }]
                startWith:@1];
        }]
        scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) {
            return @(running.integerValue + next.integerValue);
        }]
        map:^(NSNumber *count) {
            return @(count.integerValue > 0);
        }]
        startWith:@NO];

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

    RACSignal *moreExecutionsAllowed = [RACSignal
        if:[self.allowsConcurrentExecutionSubject startWith:@NO]
        then:[RACSignal return:@YES]
        else:[immediateExecuting not]];

    if (enabledSignal == nil) {
        enabledSignal = [RACSignal return:@YES];
    } else {
        enabledSignal = [enabledSignal startWith:@YES];
    }

    _immediateEnabled = [[[[RACSignal
        combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
        and]
        takeUntil:self.rac_willDeallocSignal]
        replayLast];

    _enabled = [[[[[self.immediateEnabled
        take:1]
        concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
        distinctUntilChanged]
        replayLast]
        setNameWithFormat:@"%@ -enabled", self];

    return self;
}
我們詳細的分析一下該部分

    _addedExecutionSignalsSubject = [RACSubject new];
    _allowsConcurrentExecutionSubject = [RACSubject new];
    _signalBlock = [signalBlock copy];

    _executionSignals = [[[self.addedExecutionSignalsSubject
        map:^(RACSignal *signal) {
            return [signal catchTo:[RACSignal empty]];
        }]
        deliverOn:RACScheduler.mainThreadScheduler]
        setNameWithFormat:@"%@ -executionSignals", self];
?該部分首先創建了`_addedExecutionSignalsSubject`和`_allowsConcurrentExecutionSubject`兩個subject。之後就是最重要的`_executionSignals` 的信號。因為`_executionSignals`該信號是跟蹤參數中的`signalBlock`的信號,但是在整個函數中都沒有看到`signalBlock`和`_executionSignals`或者是`addedExecutionSignalsSubject` 的互動。搜索整個文件在函數`execute`中可以發現以下綁定的代碼
    RACSignal *signal = self.signalBlock(input);
    NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);

    // We subscribe to the signal on the main thread so that it occurs _after_
    // -addActiveExecutionSignal: completes below.
    //
    // This means that `executing` and `enabled` will send updated values before
    // the signal actually starts performing work.
    RACMulticastConnection *connection = [[signal
        subscribeOn:RACScheduler.mainThreadScheduler]
        multicast:[RACReplaySubject subject]];

    [self.addedExecutionSignalsSubject sendNext:connection.signal];

    [connection connect];
該函數中對signalBlock信號進行multicast。代碼中的注釋提到:我們訂閱信號到主線程上,這樣使得在信號真正開始工作之前`executing` 和 `enabled` 將會被發送更新 對於multicast不了解的可以查看[細說ReactiveCocoa的冷信號與熱信號](http://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-1.html) ?但是這個函數什麼是被調用呢?函數`execute`的執行必須是在`- (id)initWithEnabled:nil signalBlock:(RACSignal * (^)(id input))signalBlock;` 本文是采用UIButton作為例子的跟蹤UIButton+RACCommandSupport.m文件,可以看到
- (void)rac_commandPerformAction:(id)sender {
    [self.rac_command execute:sender];
}
在不同的控件中,都有相應的調用`execute`。例如在UIRefreshControl+RACCommandSupport.m文件中有如下調用函數`execute`
    RACDisposable *executionDisposable = [[[[[self
        rac_signalForControlEvents:UIControlEventValueChanged]
        map:^(UIRefreshControl *x) {
            return [[[command
                execute:x]
                catchTo:[RACSignal empty]]
                then:^{
                    return [RACSignal return:x];
                }];
        }]
        concat]
        deliverOnMainThread]
        subscribeNext:^(UIRefreshControl *x) {
            [x endRefreshing];
        }];
接下來繼續分析
    RACMulticastConnection *errorsConnection = [[[self.addedExecutionSignalsSubject
        flattenMap:^(RACSignal *signal) {
            return [[signal
                ignoreValues]
                catch:^(NSError *error) {
                    return [RACSignal return:error];
                }];
        }]
        deliverOn:RACScheduler.mainThreadScheduler]
        publish];

    _errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
    [errorsConnection connect];
關於這部分源碼給出了以下解釋
   // `errors` needs to be multicasted so that it picks up all
    // `activeExecutionSignals` that are added.
    //
    // In other words, if someone subscribes to `errors` _after_ an execution
    // has started, it should still receive any error from that execution.
代碼中的注釋一般都是很重要的。如果有人在執行開始之後訂閱了`error`信號,仍然可以收到`error`信號。這個函數也是比較簡單就是捕捉到`addedExecutionSignalsSubject`到其中的`error`通過RACSignal中的return函數multicast。 繼續
   RACSignal *immediateExecuting = [[[[self.addedExecutionSignalsSubject
        flattenMap:^(RACSignal *signal) {
            return [[[signal
                catchTo:[RACSignal empty]]
                then:^{
                    return [RACSignal return:@-1];
                }]
                startWith:@1];
        }]
        scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) {
            return @(running.integerValue + next.integerValue);
        }]
        map:^(NSNumber *count) {
            return @(count.integerValue > 0);
        }]
        startWith:@NO];

    _executing = [[[[[immediateExecuting
        deliverOn:RACScheduler.mainThreadScheduler]
        // This is useful before the first value arrives on the main thread.
        startWith:@NO]
        distinctUntilChanged]
        replayLast]
        setNameWithFormat:@"%@ -executing", self];
該部分函數主要是根據addedExecutionSignalsSubject信號是否是空信號來判斷executing來判斷command是否在執行。 最後一部分
    RACSignal *moreExecutionsAllowed = [RACSignal
        if:[self.allowsConcurrentExecutionSubject startWith:@NO]
        then:[RACSignal return:@YES]
        else:[immediateExecuting not]];

    if (enabledSignal == nil) {
        enabledSignal = [RACSignal return:@YES];
    } else {
        enabledSignal = [enabledSignal startWith:@YES];
    }

    _immediateEnabled = [[[[RACSignal
        combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
        and]
        takeUntil:self.rac_willDeallocSignal]
        replayLast];

    _enabled = [[[[[self.immediateEnabled
        take:1]
        concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
        distinctUntilChanged]
        replayLast]
        setNameWithFormat:@"%@ -enabled", self];
該部分主要是根據allowsConcurrentExecutionSubject和enabledSignal來設置`enabled`的相關屬性 ##實例展示 ?為了避免紙上談書,所以還是需要代碼去驗證相關的使用,為了不使用nslog給出一個可視化的結果我們通過在main.stroyboard中添加一個textView和Uibutton。在實際項目中不推薦使用storyboard,建議使用masonry來做布局工作。一個基本的顯示 第二次測試

通過以下語句把我們想要的信息打印到textview中去

      self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"signal-done\n"] ];;
因為只是為了做測驗,沒有嚴格的按照mvvm的模式來寫,我們把全部的邏輯都放在了view controller中進行實現,這也是為什麼mvvm被需要的原因,vc太繁雜了
    _btn.rac_command=
    [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
        @strongify(self)
        NSLog(@"button was pressed!");
        return [[[RACSignal createSignal:^RACDisposable *(id subscriber) {
            [subscriber sendNext:@"111111"];
       //  [subscriber sendError:[NSError errorWithDomain:@"" code:120 userInfo:nil]];
            [subscriber sendCompleted];
           self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"signal-done\n"] ];;
            return nil;

        }]initially:^{
            self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"initially\n"] ];;
        }]finally:^{
            self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"finally\n"] ];;
        }];
    }];
    [[self.btn.rac_command.executionSignals flatten]
     subscribeNext:^(id x) {
         self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"subsribenext%@\n",x] ];;
     }];

    [self.btn.rac_command.errors
     subscribeNext:^(NSError *error) {
         self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"error=%@\n",error] ];;
     }];
當點擊Button的時候,會出現以下的結果 第二次測試

可以看到在上面的過程當中先執行了initially然後block中的信號發出了一個sendNext信號,這個信號采用multicas的形式轉發出去,上文executionSignals其實是block中信號的一個map,但是由於executionSignals是一個signal of signals,所以在這裡采用的是flatten的形式將它轉化為signal,其實這個轉化,存在以下幾種方法。

第一種

        [self.btn.rac_command.executionSignals
         subscribeNext:^(RACSignal * subscribeSignal) {
             [subscribeSignal subscribeNext:^(id x) {
                 self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"subsribenext%@\n",x] ];; ;
             } error:^(NSError *error) {
                 ;
             }];

         }];
因為采用的是信號的信號,所以第一次subscribeNext的出來的是RACSignal,所以需要在subscribeNext一次才能得到信號內的內容。 **第二種**
    [[self.btn.rac_command.executionSignals switchToLatest]
     subscribeNext:^(id x) {
         self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"subsribenext%@\n",x] ];;
     }];
這中方法的關鍵是函數switchToLatest,這個函數接受的一定是一個signal of signals,返回的是所有的signal of signals中最新的一個signal 第三種方法
    [[self.btn.rac_command.executionSignals flattenMap:^RACStream *(RACSignal* subscribeSignal) {
        return subscribeSignal;
    }]
     subscribeNext:^(id x) {
         self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"subsribenext%@\n",x] ];;
     }];
其實flatten方法就是等同於該方法flattenMap
- (instancetype)flatten {
    return [[self flattenMap:^(id value) {
        return value;
    }] setNameWithFormat:@"[%@] -flatten", self.name];
}
**完了??** **木有** 發現沒有上文中是不能接受到block中的completed信號,並對其進行處理?? 在介紹另外一種實現的時候,我們先在block嘗試發送error看看結果 第二次測試

可以看到error在rac_command.errors被捕捉並且被執行了。
下面看一下另外一種實現

    //正確的執行方法1
    _btn.rac_command=
    [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
      @strongify(self)
          self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"button was pressed\n"] ];

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

            [subscriber sendNext:@"111111"];
         // [subscriber sendError:[NSError errorWithDomain:@"" code:120 userInfo:nil]];
          [subscriber sendCompleted];
            return nil;
        }]materialize]
                 initially:^{
              self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"block-begin\n"] ];;
        }]
                finally:^{
              self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"block-end\n"] ];;
        }];
    }];
    [self.btn.rac_command.executionSignals subscribeNext:^(RACSignal *sig) {
        [[[sig dematerialize]deliverOn:[RACScheduler mainThreadScheduler]]subscribeNext:^(id x) {
            self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"subscribeNext%@\n",x] ];
        }error:^(NSError *error) {
           self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"error==%@\n",error] ];
        } completed:^{
            self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"completed!\n"] ];
        }];
    }];
我們將相應的結果和第一種實現進行比較 發送next和Completed的對比 第三次測試

繼續
發送next和error的對比

第四次測試

首先上述的兩張圖片當中右半部分都是采用第二種方法實現的,可以看到在第二種方法當中我們有捕捉到了completed信號,但是同時我們也發現了右邊部分的信號的輸出順序和左邊部分采用第一種方法的輸出是不一樣的。
先簡單的說明一下第二種方法的實現原理首先采用materialize函數對block發出的信號進行一個封裝,繼續跟蹤代碼,我們可以發現materialize的源碼中

- (RACSignal *)materialize {
    return [[RACSignal createSignal:^(id subscriber) {
        return [self subscribeNext:^(id x) {
            [subscriber sendNext:[RACEvent eventWithValue:x]];
        } error:^(NSError *error) {
            [subscriber sendNext:[RACEvent eventWithError:error]];
            [subscriber sendCompleted];
        } completed:^{
            [subscriber sendNext:RACEvent.completedEvent];
            [subscriber sendCompleted];
        }];
    }] setNameWithFormat:@"[%@] -materialize", self.name];
}

可以看到我們對相關的
signal(sendNext,error,Completed)進行了封裝為sendNext。

    [self.btn.rac_command.executionSignals subscribeNext:^(RACSignal *sig) {
        [[[sig dematerialize]deliverOn:[RACScheduler mainThreadScheduler]]subscribeNext:^(id x) {
            self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"subscribeNext%@\n",x] ];
        }error:^(NSError *error) {
           self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"error==%@\n",error] ];
        } completed:^{
            self.textview.text=[self.textview.text stringByAppendingString:  [NSString stringWithFormat:@"completed!\n"] ];
        }];
    }];

通過采用dematerialize函數,之前封裝的信號,進行解封裝.因為error在block中被封裝了所以只有采用dematerialize才能顯示。

這裡要繼續分析這兩種結果產生的順序不同的一個原因。

//待了解

上述的兩種方法中存在著一個很大的問題是二者的

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved