You are right. Building this functionality into view controllers is unnecessary and poor encapsulation.
In the MVC paradigm, Models often have a Data Context. Data Contexts manage communication with the backend store (in iOS, this tends to be a web service or a local file) to populate and archive the Model objects. For an authenticated Data Context, you have a property for username, password and the authentication state.
@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
The authenticated state can be a simple boolean or an integer if you want to track many states (authenticated, unauthenticated, unauthenticated after attempting authentication with stored credentials). You can now observe the authenticationState
property to allow your controller layer to take action on changes in authentication state.
When requesting data from your web service, you change the authentication state when the server refuses the request due to invalid credentials
-(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;
}
The Controller is your app delegate. It stores instance of our DataContext. It observes changes to that authenticated
property and displays a view or reattempts authentication when appropriate.
- (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];
}
}
In your storyboard, you can basically pretend authentication doesn't exist because your app delegate interjects the user interface for authentication whenever the data context communicates it's needed.