RxSwift で MVVM を作成しようとしていますが、Objective-C の ReactiveCocoa で行っていたことと比較すると、正しい方法でサービスを作成するのが少し難しくなっています。
例として、ログイン サービスがあります。
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)
と私のviewModel
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) ユーザー名とパスワードのフィールドをパラメーターとしてビューモデルに送信する必要があります (ちなみに、その方が解決しやすいです)
2)ログインボタンがタップされたときにviewControllerがすべての作業を行う方法が好きではありません。viewControllerは、ログインアクセスを取得するためにどのサービスを呼び出す必要があるかを知る必要はありません。これはviewModelの仕事です。
3 ) サブスクリプションの外部にあるユーザー名とパスワードの保存された値にアクセスできません。
これを行う別の方法はありますか?Rx'ersはこの種のことをどのようにしていますか?どうもありがとう。