【发布时间】:2014-04-18 19:19:51
【问题描述】:
我刚刚开始更新我的 ReactiveCocoa 应用程序以使用 MVVM 模式,并且对 ViewController 和 ViewModel 之间的边界以及 ViewController 应该有多笨有一些疑问。
我要更新的应用程序的第一部分是登录流程,其行为如下。
- 用户输入电子邮件地址、密码并点击登录按钮
- 成功的响应包含一个或多个
User模型 - 这些
User模型与注销按钮一起显示 - 在关闭登录视图并显示主视图之前,必须为会话选择
User模型。
MVVM 之前
-
LoginViewController直接处理LoginButton命令 -
LoginButton命令直接与SessionManager对话 -
LoginViewController显示UIActionSheet用于选择User模型或注销 -
LoginViewController的用户选择和注销功能直接与SessionManager对话
MVVM 之后
-
LoginViewModel公开了登录命令以及用户选择和注销方法 -
LoginViewModel用户选择和注销方法直接与SessionManager对话 -
LoginViewController对LoginViewModel的登录命令作出反应 -
LoginViewController显示UIActionSheet用于选择User模型或注销 -
LoginViewController的用户选择和注销功能与LoginViewModel通话
LoginViewModel.h
@interface LoginViewModel : RVMViewModel
@property (strong, nonatomic, readonly) RACCommand *loginCommand;
@property (strong, nonatomic, readonly) RACSignal *checkingSessionSignal;
@property (strong, nonatomic, readonly) NSArray *users;
@property (strong, nonatomic) NSString *email;
@property (strong, nonatomic) NSString *password;
- (void)logout;
- (void)switchToUserAtIndex:(NSUInteger)index;
@end
LoginViewModel.m
@implementation LoginViewModel
- (instancetype)init {
self = [super init];
if (self) {
@weakify(self);
// Set up the login command
self.loginCommand = [[RACCommand alloc] initWithEnabled:[self loginEnabled]
signalBlock:^RACSignal *(id input) {
@strongify(self);
[[[SessionManager sharedInstance] loginWithEmail:self.email
password:self.password]
subscribeNext:^(NSArray *users) {
self.users = users;
}];
return [RACSignal empty];
}];
// Observe the execution state of the login command
self.loggingIn = [[self.loginCommand.executing first] boolValue];
}
return self;
}
- (void)logout {
[[SessionManager sharedInstance] logout];
}
- (void)switchToUserAtIndex:(NSUInteger)index {
if (index < [self.users count]) {
[[SessionManager sharedInstance] switchToUser:self.users[index]];
}
}
- (RACSignal *)loginEnabled {
return [RACSignal
combineLatest:@[
RACObserve(self, email),
RACObserve(self, password),
RACObserve(self, loggingIn)
]
reduce:^(NSString *email, NSString *password, NSNumber *loggingIn) {
return @([email length] > 0 &&
[password length] > 0 &&
![loggingIn boolValue]);
}];
}
@end
LoginViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
@weakify(self);
// Bind to the view model
RAC(self.controlsContainerView, hidden) = self.viewModel.checkingSessionSignal;
RAC(self.viewModel, email) = self.emailField.rac_textSignal;
RAC(self.viewModel, password) = self.passwordField.rac_textSignal;
self.loginButton.rac_command = self.viewModel.loginCommand;
self.forgotPasswordButton.rac_command = self.viewModel.forgotPasswordCommand;
// Respond to the login command execution
[[RACObserve(self.viewModel, users)
skip:1]
subscribeNext:^(NSArray *users) {
@strongify(self);
if ([users count] == 0) {
[Utils presentMessage:@"Sorry, there appears to be a problem with your account."
withTitle:@"Login Error"
level:MessageLevelError];
} else if ([users count] == 1) {
[self.viewModel switchToUserAtIndex:0];
} else {
[self showUsersList:users];
}
}];
// Respond to errors from the login command
[self.viewModel.loginCommand.errors
subscribeNext:^(id x) {
[Utils presentMessage:@"Sorry, your login credentials are incorrect."
withTitle:@"Login Error"
level:MessageLevelError];
}];
}
- (void)showUsersList:(NSArray *)users {
CCActionSheet *sheet = [[CCActionSheet alloc] initWithTitle:@"Select Organization"];
// Add buttons for each of the users
[users eachWithIndex:^(User *user, NSUInteger index) {
[sheet addButtonWithTitle:user.organisationName block:^{
[self.viewModel switchToUserAtIndex:index];
}];
}];
// Add a button for cancelling/logging out
[sheet addCancelButtonWithTitle:@"Logout" block:^{
[self.viewModel logout];
}];
// Display the action sheet
[sheet showInView:self.view];
}
@end
问题
- 创建额外的 ViewModel 层意味着我需要代理
SessionManager调用。我想将LoginViewController与SessionManager解耦的好处超过了 ViewModel 层的额外代码和函数调用? -
LoginViewController了解User模型,以便显示可以选择的用户列表。这打破了 MVVM 模式,当然感觉不对。LoginViewModel是否应该只提取LoginViewController所需的User模型的必要属性并将它们添加到字典中,其中的数组返回到LoginViewController?或者在LoginViewModel上有一个方法会更好,它返回给定索引的用户名,允许LoginViewController显示这个名称?我知道 ViewModel 负责弥合模型和视图之间的差距,但这确实感觉像是双重处理。根据我对第一个问题的预感,我猜想分离这些关注点的好处远远超过了感觉有点费力的映射过程。 - 如果
LoginViewModel调用包含在SessionManager中的所有功能,是否只针对LoginViewModel编写测试就足够了,还是应该专门针对SessionManager编写测试?
【问题讨论】:
标签: ios mvvm reactive-cocoa