Шаблон делегата в среде Cocoa, обязанности которого описываются в протоколе, можно вполне сымитировать в прикладном коде.

Несмотря на то что для настройки этого шаблона требуется некоторый практический опыт и определенное время, такой подход зачастую оказывается наиболее подходящим, поскольку это позволяет оптимально распределить знания и ответственность среди различных вовлеченных объектов. Характерным тому примером служит двухсторонняя связь, когда объект А создает и настраивает объект В, а перед тем, как объект В прекратит свое существование, он устанавливает обратную связь с объектом А.

Действие шаблона в данном примере состоит в том, что в заголовочном файле класса В определяется протокол SomeProtocol наряду со свойством delegate, обозначаемым как id <SomeProtocol>. Таким образом, класс А импортирует заголовочный файл из класса В и настраивает его, что вполне приемлемо и правильно, поскольку объект А собирается создать и настроить объект В. Однако классу В совсем не обязательно знать о классе А, что также приемлемо и правильно, поскольку его основная обязанность — лишь обслуживать любой другой объект. Поскольку класс А импортирует заголовочный файл из класса В, то ему известно о протоколе SomeProtocol, и он может принять и реализовать его обязательные методы. Когда объект А создает объект В, он также устанавливает себя в качестве делегата объекта В. Теперь объекту В известно все, что он должен знать, а именно: он может посылать сообщения по протоколу SomeProtocol своему делегату независимо от того, к какому именно классу принадлежит этот делегат.

Для того чтобы стало понятнее, почему в данном случае подходит именно такой шаблон, обратимся к конкретному примеру. В одном из моих приложений отображается представление, где пользователь может перемещать три ползунка для выбора цвета. Соответственно код этого представления находится в классе ColorPickerController. Когда пользователь нажимает кнопку Done или Cancel, представление должно быть удалено с экрана, но прежде в коде, отображающем это представление, нужно выяснить, какой именно цвет выбрал пользователь. Для этого нужно послать сообщение от экземпляра класса ColorPickerController обратно экземпляру класса, отображающего представление. Ниже приведено объявление этого сообщения.

- (void) colorPicker:(ColorPickerController*)picker

didSetColorNamed:(NSString*)theName toColor:(UlColor*)theColor;

Невольно возникает вопрос: где должно быть размещено это объявление? В моем приложении экземпляр, который, по существу, отображает представление типа ColorPicker Controller, относится к классу SettingsController. Поэтому приведенный выше метод можно было бы объявить в разделе интерфейса заголовочного файла класса SettingsController и затем организовать импорт этого файла в классе Color PickerController. Однако такой подход оказывается неверным по следующим причинам.

  • В обязанности класса SettingsController не должно входить объявление метода, реализуемого только из уважения к классу ColorPickerController.
  • Если рассматриваемый здесь метод объявляется в заголовочном файле класса SettingsController, то этот файл должен быть импортирован в классе ColorPickerController, чтобы послать сообщение. Однако это означает, что классу ColorPickerController теперь известно все о классе SettingsController, тогда как ему следует знать лишь то, что в данном классе реализуется рассматриваемый здесь метод.
  • Вполне возможно, что экземпляр, посылающий данное сообщение, относится к классу SettingsController. Он должен быть доступен для любого класса, чтобы отобразить или удалить с экрана представление типа ColorPickerController. Следовательно, он уполномочен получить данное сообщение.

Следовательно, требуется, чтобы в классе ColorPickerController был объявлен метод, который должен вызываться из этого же класса, а также отправлять вслепую сообщение некоторому получателю без учета принадлежности последнего к какому-то конкретному классу Для этого потребуется установить фактическую связь между объявлением данного метода в классе ColorPickerController и его реализацией в классе получателя. Именно такую связь и устанавливает протокол! Итак, в заголовочном файле класса ColorPickerController следует определить протокол вместе с входящим в него данным методом, а в классе, отображающем представление типа ColorPickerController, — установить соответствие этому протоколу. Кроме того, в классе ColorPickerController имеется свойство, обозначаемое как delegate или аналогичным образом. Этим обеспечивается канал связи для передачи сообщения, а компилятор уведомляется, что отправка данного сообщения является вполне допустимой.

Ниже приведено содержимое заголовочного файла ColorPickerController. Обратите внимание на применение предваряющего объявления, упоминавшегося в главе 10.

@protocol ColorPickerDelegate;

@interface ColorPickerController : UIViewController

@property (nonatomic, weak) id <ColorPickerDelegate> delegate;

@end

Qprotocol ColorPickerDelegate

// color == nil при отмене

- (void) colorPicker: (ColorPickerController *)picker

didSetColorNamed:(NSString *)theName toColor:(UlColor*)theColor;

@end

Когда в экземпляре класса SettingsController создается и настраивается экземпляр класса ColorPickerController, то он устанавливает себя также в качестве делегата для экземпляра класса ColorPickerController. Если теперь пользователь выберет цвет, то

экземпляру класса ColorPickerController станет известно, куда нужно отправить сообщение colorPicker:didSetColorNamed: toColor:, а именно: его делегату. Компилятор позволит это сделать, поскольку делегат принял протокол ColorPickerDelegate, как показано ниже.

- (void) dismissColorPicker:              (id)          sender   {

// пользователь нажал кнопку Done [self.delegate colorPicker:self didSetColorNamed:self.colorName toColor:self.color] ;

}

Если создать в среде Xcode проект из шаблона Utility Application, то можно обнаружить, что и в нем воплощается та же самая архитектура. Этот шаблон начинается с класса MainViewController, а завершается созданием класса FlipsideViewController. Когда объект типа FlipsideViewController готов прекратить свое существование, ему требуется отправить сообщение flipsideViewControllerDidFinish: обратно тому объекту, который его создал. Таким образом, в классе FlipsideViewController определяется протокол FlipsideViewControllerDelegate, требующий реализации метода flipsideVie wControllerDidFinish: наряду со свойством delegate, обозначаемым как id <Flipsi deViewControllerDelegated

Когда экземпляр класса MainViewController создает экземпляр класса Flipside ViewController, он устанавливает себя в качестве делегата для экземпляра класса FlipsideViewController. Он действительно может это сделать, поскольку в классе MainViewController принят протокол FlipsideViewControllerDelegate! Задача решена, и дело с концом. Если же у вас возникнут сомнения по поводу настройки шаблона делегата и протокола, создайте проект по шаблону Utility Application и тщательно изучите его.


 

 

 

Добавить комментарий