我正在尝试使用 RxSwift 编写一个 MVVM,并且与我过去在 ReactiveCocoa for Objective-C 中所做的相比,以正确的方式编写我的服务有点困难。
一个例子是登录服务。
使用 ReactiveCocoa (Objective-C) 我编写如下代码:
// ViewController
// send textfield inputs to viewmodel
RAC(self.viewModel, userNameValue) = self.fieldUser.rac_textSignal;
RAC(self.viewModel, userPassValue) = self.fieldPass.rac_textSignal;
// set button action
self.loginButton.rac_command = self.viewModel.loginCommand;
// subscribe to login signal
[[self.viewModel.loginResult deliverOnMainThread] subscribeNext:^(NSDictionary *result) {
// implement
} error:^(NSError *error) {
NSLog(@"error");
}];
我的 viewModel 应该是这样的:
// valid user name signal
self.isValidUserName = [[RACObserve(self, userNameValue)
map:^id(NSString *text) {
return @( text.length > 4 );
}] distinctUntilChanged];
// valid password signal
self.isValidPassword = [[RACObserve(self, userPassValue)
map:^id(NSString *text) {
return @( text.length > 3);
}] distinctUntilChanged];
// merge signal from user and pass
self.isValidForm = [RACSignal combineLatest:@[self.isValidUserName, self.isValidPassword]
reduce:^id(NSNumber *user, NSNumber *pass){
return @( [user boolValue] && [pass boolValue]);
}];
// login button command
self.loginCommand = [[RACCommand alloc] initWithEnabled:self.isValidForm
signalBlock:^RACSignal *(id input) {
return [self executeLoginSignal];
}];
现在在 RxSwift 中,我编写的内容与以下内容相同:
// ViewController
// initialize viewmodel with username and password bindings
viewModel = LoginViewModel(withUserName: usernameTextfield.rx_text.asDriver(), password: passwordTextfield.rx_text.asDriver())
// subscribe to isCredentialsValid 'Signal' to assign button state
viewModel.isCredentialsValid
.driveNext { [weak self] valid in
if let button = self?.signInButton {
button.enabled = valid
}
}.addDisposableTo(disposeBag)
// signinbutton
signInButton.rx_tap
.withLatestFrom(viewModel.isCredentialsValid)
.filter { $0 }
.flatMapLatest { [unowned self] valid -> Observable<AutenticationStatus> in
self.viewModel.login(self.usernameTextfield.text!, password: self.passwordTextfield.text!)
.observeOn(SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .Default))
}
.observeOn(MainScheduler.instance)
.subscribeNext {
print($0)
}.addDisposableTo(disposeBag)
我正在以这种方式更改按钮状态,因为我不能这样做:
viewModel.isCredentialsValid.drive(self.signInButton.rx_enabled).addDisposableTo(disposeBag)
和我的视图模型
let isValidUser = username
.distinctUntilChanged()
.map { $0.characters.count > 3 }
let isValidPass = password
.distinctUntilChanged()
.map { $0.characters.count > 2 }
isCredentialsValid = Driver.combineLatest(isValidUser, isValidPass) { $0 && $1 }
和
func login(username: String, password: String) -> Observable<AutenticationStatus>
{
return APIServer.sharedInstance.login(username, password: password)
}
我使用 Driver 是因为它包含了一些不错的功能,例如:catchErrorJustReturn(),但我真的不喜欢我这样做的方式:
1)我必须将用户名和密码字段作为参数发送到viewModel(顺便说一句,这更容易解决)
2 ) 我不喜欢我的 viewController 在点击登录按钮时完成所有工作的方式,viewController 不需要知道它应该调用哪个服务来获取登录访问权限,这是一个 viewModel 工作。
3 ) 我无法在订阅之外访问用户名和密码的存储值。
有不同的方法可以做到这一点吗?你们 Rx'ers 是怎么做这种事情的?非常感谢。