Ответ 1
Я бы предложил как iPatel, чтобы использовать delegation для решения вашей проблемы. Связь между родительским контроллером представления и контроллером входа в систему делает этот шаблон подходящим. Когда один объект создает другой, чтобы выполнить определенную ответственность, следует рассматривать делегирование как способ связать созданный объект с создателем. Особенно убедительной причиной выбора делегирования было бы, если задача, которая должна быть выполнена, потенциально имеет несколько шагов, требующих высокого уровня взаимодействия между объектами. Вы можете посмотреть на NSURLConnectionDelegate
protocol в качестве иллюстрации этого. Подключение к URL-адресу - сложная задача, включающая такие этапы, как обработка ответов, решение проблем аутентификации, сохранение загруженных данных и обработка ошибок, соединение и делегат обрабатывают это вместе в течение всего срока службы соединения.
Как вы, наверное, заметили, в Objective-C протоколы используются для достижения делегирования без жесткой привязки созданного объекта (в данном случае вашего контроллера входа в систему) к объекту, который его создал (родительский контроллер представления). Контроллер просмотра входа может затем взаимодействовать с любым объектом, который может получать сообщения, определенные в его протоколе, а не полагаться на какую-либо конкретную реализацию класса. Завтра, если вы получите требование разрешить любому диспетчеру просмотра отображать окно входа в систему, контроллер входа в систему не потребуется изменять. Ваши другие контроллеры представлений могут реализовать свой протокол делегатов, создать и представить окно входа в систему и назначить себя делегатами без контроллера входа в систему, когда-либо зная о своем существовании.
Некоторые примеры делегирования, которые вы найдете в Stack Overflow, могут быть очень запутанными и очень не похожими на то, что найдено во встроенных фреймворках. Нужно тщательно выбирать имена и интерфейсы протоколов, а также обязанности, назначенные каждому объекту, чтобы максимально использовать повторное использование кода и цель кода.
Сначала вы должны взглянуть на многие протоколы делегатов внутри встроенных фреймворков, чтобы понять, как выглядит отношение при выражении кода. Вот еще один небольшой пример, основанный на вашем случае использования вашего логина. Надеюсь, вы обнаружите, что цель делегации ясна и что роли и обязанности задействованных объектов ясны и выражаются через их имена в коде.
Сначала рассмотрим протокол делегата LoginViewController:
#import <UIKit/UIKit.h>
@protocol LoginViewControllerDelegate;
@interface LoginViewController : UIViewController
// We choose a name here that expresses what object is doing the delegating
@property (nonatomic, weak) id<LoginViewControllerDelegate> delegate;
@end
@protocol LoginViewControllerDelegate <NSObject>
// The methods declared here are all optional
@optional
// We name the methods here in a way that explains what the purpose of each message is
// Each takes a LoginViewController as the first argument, allowing one object to serve
// as the delegate of many LoginViewControllers
- (void)loginViewControllerDidLoginSuccessfully:(LoginViewController *)lvc;
- (void)loginViewController:(LoginViewController *)lvc didFailWithError:(NSError *)error;
- (void)loginViewControllerDidReceivePasswordResetRequest:(LoginViewController *)lvc;
- (void)loginViewControllerDiDReceiveSignupRequest:(LoginViewController *)lvc;
- (BOOL)loginViewControllerShouldAllowAnonymousLogin:(LoginViewController *)lvc;
@end
Контроллер входа может передавать несколько событий своему делегату, а также запрашивать у своего делегата информацию, используемую для настройки его поведения. Он передает события делегату в его реализацию как часть своего ответа на действия пользователя:
#import "LoginViewController.h"
@interface LoginViewController ()
@property (weak, nonatomic) IBOutlet UIButton *anonSigninButton;
@end
@implementation LoginViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Here we ask the delegate for information used to layout the view
BOOL anonymousLoginAllowed = NO;
// All our protocol methods are @optional, so we must check they are actually implemented before calling.
if ([self.delegate respondsToSelector:@selector(loginViewControllerShouldAllowAnonymousLogin:)]) {
// self is passed as the LoginViewController argument to the delegate methods
// in this way our delegate can serve as the delegate of multiple login view controllers, if needed
anonymousLoginAllowed = [self.delegate loginViewControllerShouldAllowAnonymousLogin:self];
}
self.anonSigninButton.hidden = !anonymousLoginAllowed;
}
- (IBAction)loginButtonAction:(UIButton *)sender
{
// We're preteneding our password is always bad. So we assume login succeeds when allowed anonmously
BOOL loginSuccess = [self isAnonymousLoginEnabled];
NSError *loginError = [self isAnonymousLoginEnabled] ? nil : [NSError errorWithDomain:@"domain" code:0 userInfo:nil];
// Fake concurrency
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Notify delegate of failure or success
if (loginSuccess) {
if ([self.delegate respondsToSelector:@selector(loginViewControllerDidLoginSuccessfully:)]) {
[self.delegate loginViewControllerDidLoginSuccessfully:self];
}
}
else {
if ([self.delegate respondsToSelector:@selector(loginViewController:didFailWithError:)]) {
[self.delegate loginViewController:self didFailWithError:loginError];
}
}
});
}
- (IBAction)forgotPasswordButtonAction:(id)sender
{
// Notify delegate to handle forgotten password request.
if ([self.delegate respondsToSelector:@selector(loginViewControllerDidReceivePasswordResetRequest:)]) {
[self.delegate loginViewControllerDidReceivePasswordResetRequest:self];
}
}
- (IBAction)signupButtonAction:(id)sender
{
// Notify delegate to handle signup request.
if ([self.delegate respondsToSelector:@selector(loginViewControllerDiDReceiveSignupRequest:)]) {
[self.delegate loginViewControllerDiDReceiveSignupRequest:self];
}
}
- (BOOL)isAnonymousLoginEnabled
{
BOOL anonymousLoginAllowed = NO;
if ([self.delegate respondsToSelector:@selector(loginViewControllerShouldAllowAnonymousLogin:)]) {
anonymousLoginAllowed = [self.delegate loginViewControllerShouldAllowAnonymousLogin:self];
}
return anonymousLoginAllowed;
}
@end
Главный контроллер представления создает экземпляр и представляет контроллер входа в систему и обрабатывает его сообщения делегатов:
#import "MainViewController.h"
#import "LoginViewController.h"
#define LOGGED_IN NO
@interface MainViewController () <LoginViewControllerDelegate>
@end
@implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Fake loading time to show the modal cleanly
if (!LOGGED_IN) {
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Create a login view controller, assign its delegate, and present it
LoginViewController *lvc = [[LoginViewController alloc] init];
lvc.delegate = self;
[self presentViewController:lvc animated:YES completion:^{
NSLog(@"modal completion finished.");
}];
});
}
}
#pragma mark - LoginViewControllerDelegate
- (void)loginViewControllerDidLoginSuccessfully:(LoginViewController *)lvc
{
NSLog(@"Login VC delegate - Login success!");
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (void)loginViewController:(LoginViewController *)lvc didFailWithError:(NSError *)error
{
// Maybe show an alert...
// UIAlertView *alert = ...
}
- (void)loginViewControllerDidReceivePasswordResetRequest:(LoginViewController *)lvc
{
// Take the user to safari to reset password maybe
NSLog(@"Login VC delegate - password reset!");
}
- (void)loginViewControllerDiDReceiveSignupRequest:(LoginViewController *)lvc
{
// Take the user to safari to open signup form maybe
NSLog(@"Login VC delegate - signup requested!");
}
- (BOOL)loginViewControllerShouldAllowAnonymousLogin:(LoginViewController *)lvc
{
return YES;
}
@end
Вход в систему может быть сложным, интерактивным процессом в некотором роде, поэтому я рекомендую вам серьезно рассмотреть возможность использования делегирования вместо уведомлений. Однако одна вещь, которая может быть проблематичной, состоит в том, что делегаты - это обязательно только один объект. Если вам нужно, чтобы несколько, разрозненные объекты знали о прогрессе контроллера входа в систему, и вам может понадобиться использовать уведомления. Особенно, если процесс входа в систему может быть очень простым, таким образом, чтобы не требовать какого-либо взаимодействия, кроме передачи односторонних сообщений и данных, уведомления могут стать жизнеспособным вариантом. Вы можете передавать произвольные переменные в уведомлении обратно в свойство userInfo
, которое является NSDictionary
того, что вы решили использовать в нем. Уведомления могут влиять на производительность, но я понимаю, что это происходит только сейчас, когда число наблюдателей составляет сотни. Тем не менее, это не самый естественный подход в моем сознании, поскольку у вас есть родительский объект (который более или менее контролирует время жизни ребенка), запрашивая у стороннего объекта обновления из дочернего объекта.