學習ReactiveCocoa中的一個疑惑

最近在學習ReactiveCocoa,驚異于響應式編程強大的能力之余,發現了一個有疑惑的點。
ReactiveCocoa的核心在于萬物皆信號,響應式編程簡單比喻就是多米諾骨牌,牽一發動全身。

網上找了些簡單demo,大多是登錄場景的小demo,輸出賬號校驗格式再提交什么的。基本代碼如下

ViewModel.h

@interface ViewModel : NSObject

@property (nonatomic, strong) NSString *account;
@property (nonatomic, strong) RACCommand *buttonCommand;

@end

ViewModel.m

- (RACCommand *)subscribeCommand {
    if (!_subscribeCommand) {
        @weakify(self);
        _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.validSignal signalBlock:^RACSignal *(id input) {
                        **
                        提交賬號密碼
                        */
            return nil;
        }];
    }
    return _subscribeCommand;
}
- (RACSignal *)accountValidSignal {
    if (!_accountValidSignal) {
        _accountValidSignal = [RACObserve(self, account) map:^id(NSString *string) {
            return @(account.length > 5);
        }];
    }
    return _accountValidSignal;
}

ViewController.m

@implementation ViewController
- (void)viewDidLoad {
    **
    省略
    */
}
- (void)bindViewModel {
    RAC(self.viewModel, account) = self.account.rac_textSignal;
    self.submitButton.rac_command = self.viewModel.buttonCommand;
}
**
省略其他部分代碼
*/
@end

很簡單的demo,響應用戶輸入TextField,ViewModel的account可以一并更新,并且實時更新button狀態。

好,看到這幾行,你是不是完全沒有疑問,一個很簡單的MVVM的小demo而已。

我昨天也是這么想的。

今天無意間看了幾行ReactiveCocoa的源碼。

RACCommand.h

@interface RACCommand : NSObject
**
省略
*/
/// A signal of whether this command is currently executing.
///
/// This will send YES whenever -execute: is invoked and the created signal has
/// not yet terminated. Once all executions have terminated, `executing` will
/// send NO.
///
/// This signal will send its current value upon subscription, and then all
/// future values on the main thread.
@property (nonatomic, strong, readonly) RACSignal *executing;

/// A signal of whether this command is able to execute.
///
/// This will send NO if:
///
///  - The command was created with an `enabledSignal`, and NO is sent upon that
///    signal, or
///  - `allowsConcurrentExecution` is NO and the command has started executing.
///
/// Once the above conditions are no longer met, the signal will send YES.
///
/// This signal will send its current value upon subscription, and then all
/// future values on the main thread.
@property (nonatomic, strong, readonly) RACSignal *enabled;
**
省略
*/
@end

發現ReactiveCocoa框架里定義的屬性,幾乎都定義為RACSignal類型,仔細想了下,確實這樣更符合ReactiveCocoa的核心思想,萬物皆信號。

看完這個之后,我決定把上面的demo改一改:

ViewModel.h

@interface ViewModel : NSObject

@property (nonatomic, strong) RACSignal<NSString *> *account;

@property (nonatomic, strong) RACCommand *command;

@end

ViewModel.m

@interface 
@end
@implementation
- (RACCommand *)subscribeCommand {
    if (!_subscribeCommand) {
        @weakify(self);
        _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.validSignal signalBlock:^RACSignal *(id input) {
                        **
                        提交賬號密碼
                        */
            return nil;
        }];
    }
    return _subscribeCommand;
}
- (RACSignal *)accountValidSignal {
    if (!_accountValidSignal) {
        _accountValidSignal = [RACObserve(self, account) map:^id(NSString *string) {
            return @(account.length > 5);
        }];
    }
    return _accountValidSignal;
}

ViewController.m

@implementation ViewController
- (void)viewDidLoad {
    **
    省略
    */
}
- (void)bindViewModel {
    self.vm.account = self.account.rac_textSignal;
    self.submitButton.rac_command = self.viewModel.buttonCommand;
}
**
省略其他部分代碼
*/
@end

這樣看代碼是不是更簡潔,更直觀。

很多人會問,那ViewModel的account值怎么從外部set呢?

這就牽涉到整體框架的數據流轉問題了。
大家知道,數據的產生,基本上有兩個源頭,一個是網絡請求,一個是用戶的action,在整個APP的開發過程中,其實我們就是一直在這兩個源頭的中間,做數據轉換和處理工作。

  • 將用戶的action傳遞給服務端,這里產生的數據,基本上都是在各種UI組件中輸入的,UIKit+ReactiveCocoa做了很強大的支持,幾乎所有輸入組件的value都已經定義了各種RACsignal輸出,我們根本不需要set,因為set這個操作應該是用戶做的,不應該是代碼做的,很完美!
  • 將服務端返回的數據展示給用戶,這里產生的數據,基本上都是在網絡請求中獲取的,或者從本地的數據庫里拉的,以用的最多的AFNetworking為例,ReactiveCocoa也不忘為AFNetworking寫了一個category,這邊的問題也解決了。

那這么寫有沒有問題呢?

有。

MVVM相較于MVC來說,不僅是解決了Controller臃腫的問題,也解決了Controller無法測試的問題,ViewModel是可以測試的。
那么如果把ViewModel中的所有屬性轉化成RACSignal類型的話,測試就無法進行了,這在選型的時候是個很重要的問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容