6

在我的应用程序委托中,我创建了一个数据模型并将其注入到我从情节提要获得的根视图控制器中,同时在需要时从一开始就请求用户的凭据。稍后,当访问某些数据模型方法时,我需要验证用户的密码并重试触发密码重新验证的请求。

最明显的是在每个可能需要请求此信息的视图控制器中构建此功能,但我想尽可能避免这种情况,因为它使控制器不那么通用,也使测试更加困难。在我看来,控制器必须对他们所提供模型的内部运作一无所知。

将这个功能添加到模型中也不适合我:管理用户交互完全超出了 MVC 中模型的职责。

谁应该负责显示带有相应视图控制器的模式对话框以让用户输入他的凭据?

4

3 回答 3

3

它可以通过回调使用非常少的代码行来完成。回调 API 将在模型层定义(因此它是可重用的),但用户交互是在控制器级别实现的(因为这是它所属的地方)。

我不完全确定您的架构到底是什么样子,根据您的描述,我假设应用程序意识到您仅在失败的请求时才经过身份验证(如果可能,您可能希望存储令牌到期日期并利用它) .

基本思路:

在您的模型中,您有一个回调块属性(例如在客户端类或您使用的任何其他模式上)。

@property (nonatomic, copy) void (^onNonauthenticatedRequest)(NSURLRequest *failedRequest, NSError *error);

当您的请求由于用户未通过身份验证而失败时,您将在模型层执行此块。

在控制器级别,您有一个控制器会提示用户输入凭据(并且具有类似的回调模式)。

client.onNonauthenticatedRequest = ^(NSURLRequest *failedRequest, NSError *error) {

    ABCredentialsViewController *credentialsViewController = [ABCredentialsViewController new];
    credentialsViewController.onAuthenticationSuccess = ^{
        // This gets called after the authentication request succeeded
        // You want to refire failedRequest here
        // Make sure you use a weak reference when using the object that owns onAuthenticationFailure
    };

    credentialsViewController.onAuthenticationFailure = ^(NSError *) {
        // You might want to do something if the user is not authenticated and failed to provide credentials
    }

    [[UIApplication sharedApplication].delegate.window.topViewController presentViewController:credentialsViewController animated:YES];
    // or you could just have a method on UIViewController/your subclass to present the credentials prompt instead
};

逻辑在正确的位置,如果您想在不同的情况下以不同的方式处理未经身份验证的请求,您可以。

于 2013-11-05T03:44:03.083 回答
1

在我看来,这里最大的要求之一是您有多个控制器可能需要呈现相同的模态对话框。对我来说,这听起来像是委托模式会很好地工作。这里的想法是保留一组模式对话框处理功能,每个控制器都可以在需要时使用。它也是 UIKit 内部用于 UITableViews 和日期选择器之类的相同模式。 https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html提供了一个概述。

于 2013-10-31T17:44:12.903 回答
0

你说的对。将此功能构建到视图控制器中是不必要的,而且封装很差。

在 MVC 范式中,模型通常具有数据上下文。数据上下文管理与后端存储的通信(在 iOS 中,这往往是 Web 服务或本地文件)以填充和归档模型对象。对于经过身份验证的数据上下文,您拥有用户名、密码和身份验证状态的属性。

@interface DataContext : NSObject
    //Authentication
    @property (nonatomic, strong) NSString * username;
    @property (nonatomic, strong) NSString * password; 
    @property (nonatomic, assign) NSInteger authenticationState;
    -(void)login;
    //Data object methods from authenticated data source (web service, etc)
    -(NSArray *)foos;
    -(NSArray *)bars;
@end

如果要跟踪许多状态(已验证、未验证、尝试使用存储的凭据进行验证后未验证),已验证状态可以是简单的布尔值或整数。您现在可以观察该authenticationState属性以允许您的控制器层对身份验证状态的更改采取措施。

从您的 Web 服务请求数据时,当服务器由于凭据无效而拒绝请求时,您会更改身份验证状态

-(NSArray *)foos
{
    NSArray * foos = nil;
    //Here you would make a web service request to populate the foos array from your web service. 
    //Here you would inspect the status code returned to capture authentication errors
    //I make my web services return status 403 unauthorized when credentials are invalid
    int statusCode = 403;
    if (statusCode == 403)
    {
         self.authenticationState = 0;//Unauthorized
    }
    return foos;
}

Controller 是您的应用程序委托。它存储我们的 DataContext 的实例。它观察对该authenticated属性的更改并在适当时显示视图或重新尝试身份验证。

- (void)observeAuthenticatedState:(NSNotification *)notification
{
    DataContext * context = [notification object];
    if (context.authenticatedState == 0)//You should have constants for state values if using NSIntegers. Assume 0 = unauthenticated.
    {
        [self.context login];
    }
    if (context.authenticatedState == -1)//You should have constants for state values if using NSIntegers. Assume -1 = unauthenticated after attempting authentication with stored credentials
    {
        UIViewController * loginController = nil;//Instantiate or use existing view controller to display username/password to user. 
        [[[self window] rootViewController] presentViewController:loginController
                                                         animated:YES
                                                       completion:nil];
    }
    if (context.authenticatedState == 1)//authenticated.
    {
        [[[self window] rootViewController] dismissViewControllerAnimated:YES
                                                               completion:nil];
    }
}

在您的故事板中,您基本上可以假装身份验证不存在,因为您的应用程序委托会在数据上下文传达需要时插入用户界面进行身份验证。

于 2013-10-30T02:59:05.210 回答