最近在學習ReactiveCocoa,驚異于響應式編程強大的能力之余恨胚,發(fā)現(xiàn)了一個有疑惑的點骂因。
ReactiveCocoa的核心在于萬物皆信號炎咖,響應式編程簡單比喻就是多米諾骨牌赃泡,牽一發(fā)動全身。
網(wǎng)上找了些簡單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狀態(tài)蓖柔。
好,看到這幾行风纠,你是不是完全沒有疑問况鸣,一個很簡單的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
發(fā)現(xiàn)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呢?
這就牽涉到整體框架的數(shù)據(jù)流轉問題了并炮。
大家知道默刚,數(shù)據(jù)的產(chǎn)生,基本上有兩個源頭逃魄,一個是網(wǎng)絡請求荤西,一個是用戶的action,在整個APP的開發(fā)過程中,其實我們就是一直在這兩個源頭的中間邪锌,做數(shù)據(jù)轉換和處理工作勉躺。
- 將用戶的action傳遞給服務端,這里產(chǎn)生的數(shù)據(jù)觅丰,基本上都是在各種UI組件中輸入的饵溅,UIKit+ReactiveCocoa做了很強大的支持,幾乎所有輸入組件的value都已經(jīng)定義了各種RACsignal輸出妇萄,我們根本不需要set蜕企,因為set這個操作應該是用戶做的,不應該是代碼做的冠句,很完美轻掩!
- 將服務端返回的數(shù)據(jù)展示給用戶,這里產(chǎn)生的數(shù)據(jù)懦底,基本上都是在網(wǎng)絡請求中獲取的唇牧,或者從本地的數(shù)據(jù)庫里拉的,以用的最多的AFNetworking為例聚唐,ReactiveCocoa也不忘為AFNetworking寫了一個category丐重,這邊的問題也解決了。
那這么寫有沒有問題呢杆查?
有扮惦。
MVVM相較于MVC來說,不僅是解決了Controller臃腫的問題根灯,也解決了Controller無法測試的問題径缅,ViewModel是可以測試的。
那么如果把ViewModel中的所有屬性轉化成RACSignal類型的話烙肺,測試就無法進行了纳猪,這在選型的時候是個很重要的問題。