UIButton使用RACCommand後setEnable方法失效的問題

問題描述

首先,我們知道RACCommand有如下初始化方法,可以傳入enabledSignal,用於控制按鈕的enable狀態。

- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;

但有時我們想自己控制按鈕的enable狀態。這個時候就可能出現標題所說的問題。標題爲了表達意思,並不準確。真正的問題是RACCommand內部調用setEnable方法,可能會覆蓋你手動設置的UIButton狀態。無論你在初始化RACommand時是否傳入enabledSignal,RACCommand內部都會控制UIButton的enable/disable狀態。我們在外部也調用setEnable方法設置enable狀態,可能會衝突。


舉個栗子

RACCommand *sendCodeCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    [sendCodeButton setEnabled:NO];
    //TODO: Send code, count down and then setEnabled:YES
    return [RACSignal empty];
}];
sendCodeButton.rac_command = sendCodeCommand;

上面這段代碼,我們的預期是點擊發送驗證碼按鈕之後disable掉按鈕,進行倒計時,倒計時結束重新enable按鈕。事實上,我們點擊按鈕之後,按鈕並沒有被disable掉。


簡單解決方案

放棄手動控制按鈕的enable狀態,在初始化RACCommand時候傳入enabledSignal,讓RACCommand統一負責。不過使用Signal控制enable狀態需要額外的轉換操作,要費些事。


RACCommand內部是如何控制UIButton enable狀態的

如果我們知道RACCommand內部是如何控制enable狀態的,我們就可以避免衝突。
我們在UIButton+RACCommandSupport.m可以找到下面這條語句:

disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];

這條語句將UIButton的enable屬性與RACCommand中的enabled信號綁定起來了,那麼我們就來看看RACCommand中的enabled信號的如何創建的,如何變化的。
我們在RACCommand.m中找到下面這段代碼:

    if (enabledSignal == nil) {
        enabledSignal = [RACSignal return:@YES];
    } else {
        enabledSignal = [[[enabledSignal
            startWith:@YES]
            takeUntil:self.rac_willDeallocSignal]
            replayLast];
    }
    _immediateEnabled = [[RACSignal
        combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
        and];

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

可以看到enabled信號受我們傳入的enabled信號(不傳,默認爲[RACSignal return:@YES])和內部的moreExecutionsAllowed信號影響。它們進行與操作。再看看moreExecutionsAllowed信號的定義。

    RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) {
        return @(activeSignals.count > 0);
    }];

    RACSignal *moreExecutionsAllowed = [RACSignal
        if:RACObserve(self, allowsConcurrentExecution)
        then:[RACSignal return:@YES]
        else:[immediateExecuting not]];

moreExecutionsAllowed信號受allowsConcurrentExecution屬性和immediateExecuting信號影響。如果允許併發,moreExecutionsAllowed發出的是@YES;如果不允許併發,目前有未完成任務,發出@NO,否則發出@YES。這裏的任務完成是指signalBlock創建出來的Signal已經complete了,signalBlock是在- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock方法中傳入的。

回到我們上面舉的栗子,在signalBlock中我們disable了sendCodeButton,但是返回了[RACSignal empty][RACSignal empty]會立即complete,activeExecutionSignals會remove掉這個signal,然後activeSignals.count變爲0,immediateExecuting發出@NO,moreExecutionsAllowed發出@YES,最終enabled信號發出@YES,sendCodeButton被enable。我們剛纔設置的狀態被沖掉了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章