ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C Часть 1

3. Для опытных

Делегирование

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

Для начала объясню в каких случаях это может быть полезным. Ярким примером использования делегирования в жизни является будильник. Фактически, вы перекладываете свои обязанности на кого-то или что-то другое. Вы «говорите» будильнику разбудить вас в определенный момент и он это делает, в определенное время подав вам сигнал.Приведу еще один пример. У вас есть большая пачка денег и вы не хотите их подсчитать. Для этого вы засовываете их в счетную машинку, которая за вас проводит подсчет. Когда деньги будут пересчитаны — машинка издаст звуковой сигнал, который вас оповещает о завершении операции и на табло вы можете увидеть результат.

Оба этих случая основаны на перекладывании ваших обязанностей на другой объект. В программировании происходит точно так же. Вы даете команду другому классу выполнить определенную операцию. По завершению выполнения — он вызовет в классе, который вы указали делегатом, метод. Этот метод и является тем сигналом как в случае с будильником или счетной машинкой.

Давайте посмотрим как это работает на практике. Для этого создадим новый проект на основе представления (View-based Application) и назовите его MyDelegate. В первую очередь создадим сам протокол. Для этого добавим в проект новый класс типа NSObject с именем MyProtocol.

Вот так выглядит интерфейс класса MyProtocol.

<code data-result="[object Object]">#import &lt;Foundation/Foundation.h&gt;

@protocol MyProtocolDelegate&lt;NSObject&gt;
@optional
- (void)endOfSomething;
- (void)endOfSomethingTwo:(NSString *)text;
@end

@interface MyProtocol : NSObject {
	id &lt;MyProtocolDelegate&gt; delegate;
}

@property (nonatomic, assign)id &lt;MyProtocolDelegate&gt; delegate;

-(void)doSomething;
-(void)doSomethingAfter:(CGFloat)sec;
-(void)ShowInfoTwo;

@end</code>

Что же мы такое написали?

Здесь мы даем имя нашему протоколу, описываем его свойства и какие методы он будет вызывать у делегата:

<code data-result="[object Object]">@protocol MyProtocolDelegate&lt;NSObject&gt;
@optional
- (void)endOfSomething;
- (void)endOfSomethingTwo:(NSString *)text;
@end</code>

При этом следует обратить внимание на слово optional. Оно означает, что у делегата не обязательно могут присутствовать эти методы. Позже, в процессе вызова этих методов мы будет проверять их наличие у делегата. Вместо optional можно использовать required. В этом случае методы, который объявлены после этого слова обязательно должны присутствовать у делегата, а при их вызове проверка не обязательна.

Здесь мы будем хранить ссылку на класс, которому следует сообщить о завершении какой-то операции (вызвать метод), поскольку делегатом может быть объект любого типа — мы указали ему общий тип id:

<code data-result="[object Object]">id &lt;MyProtocolDelegate&gt; delegate;</code>

Здесь добавлены свойства для объекта delegate (важно помнить, что делегату в свойствах мы всегда должны указывать assign, т.к. протокол обычно вызывается с этого класса (делегата) и не имеет права его удалять):

<code data-result="[object Object]">@property (nonatomic, assign)id &lt;MyProtocolDelegate&gt; delegate;</code>

Перейдем к реализации класса MyProtocol:

<code data-result="[object Object]">#import "MyProtocol.h"

@implementation MyProtocol

@synthesize delegate;

-(void)doSomething {
	SEL selector = @selector(endOfSomething);
	if (delegate &amp;&amp; [delegate respondsToSelector:selector]) {
		[delegate performSelector:selector];
	}    
}

-(void)doSomethingAfter:(CGFloat)sec {
    [self performSelector:@selector(ShowInfoTwo) withObject:nil afterDelay:sec];
}

-(void)ShowInfoTwo {
    SEL selector = @selector(endOfSomethingTwo:);
	if (delegate &amp;&amp; [delegate respondsToSelector:selector]) {
		[delegate performSelector:selector withObject:@"Something happened again"];
	}
}

@end</code>

Рассмотрим подробнее каждый метод.

-(void)doSomething
В нем мы создаем переменную типа SEL селектор. В этой переменной хранится имя функции, но не в формате строки, а в специальном, своем. Таким образом мы можем не только вызвать какой-то метод объекта, но перед этим проверить, существует ли у нашего объекта этот метода. То есть, запись типа:

<code data-result="[object Object]">SEL selector = @selector(endOfSomething);
[delegate performSelector:selector];</code>

Идентична записи:

<code data-result="[object Object]">[delegate endOfSomething];</code>

Разница лишь в том, что во втором случае мы не можем осуществить проверку на наличие такого метода у делегата. Затем мы добавляем саму проверку. В первую очередь проверяется назначен ли делегат. То есть, проверяем есть ли объект, у которого будет вызываться данная функция. Затем, с помощью метода respondsToSelector проверяем, есть ли у делегата такой метод. Это нужно для того, чтобы исключить ошибку, когда у делегата метода такого нет, а мы попытаемся его вызывать. Если оба условия истинны — вызываем сам метод делегата, используя performSelector.

-(void)ShowInfoTwo
Тело этого метода очень похоже на предыдущий. Разница лишь в том, что мы не только вызываем метод делегата, а и передаем в него некоторый параметр.

-(void)doSomethingAfter:(CGFloat)sec
Здесь мы указываем нашему классу (MyProtocol) выполнить некоторое действие через указанное время. Это тот же самый метод, который мы использовали для прятанья алерта в примере Уведомления (сообщения).

Теперь воспользуемся нашим протоколом. Изменим интерфейс класса MyDelegateViewController:

<code data-result="[object Object]">#import &lt;UIKit/UIKit.h&gt;
#import "MyProtocol.h"

@interface MyDelegateViewController : UIViewController &lt;MyProtocolDelegate&gt; {

}

@end</code>

Как видите, ничего непонятного мы здесь не написали. Импортировали класс MyProtocol и добавили протокол. Точно так же мы добавляли протокол UITableViewDelegate в случае с таблицами или UIAlertViewDelegate при работе с сообщениями.

Теперь изменим реализацию класса MyDelegateViewController:

<code data-result="[object Object]">#import "MyDelegateViewController.h"

@implementation MyDelegateViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    MyProtocol *myProtocol = [[MyProtocol new] autorelease];
	myProtocol.delegate = self;

	[myProtocol doSomething];
	[myProtocol doSomethingAfter:5.0f];
}

- (void)endOfSomething {
    NSLog(@"Something happened");
}

- (void)endOfSomethingTwo:(NSString *)text {
    NSLog(@"%@", text);
}

@end</code>

В методе viewDidLoad мы инициализируем класс MyProtocol и присваиваем в качестве делегата класс, с которого была выполнена инициализация (MyDelegateViewController), прошу обратить внимание, что делегатом может быть любой другой класс, не обязательно тот, который создает экземпляр протокола. Затем мы добавили реализацию методов, которые объявляли в протоколе. Это может быть непривычно, поскольку обычно мы пишем реализацию метод в реализации класса. Но в нашем случае нам нужно вызывать их в реализации другого класса.

При выполнении этого проекта в консоль будет выведена строка Something happened и через 5 секунд еще одна строка Something happened again.

Теперь поговорим о возможных ошибках.
Первая и самая распространенная ошибка — забыть подключить протокол. Обратите еще раз внимание на интерфейс классаMyDelegateViewController. Я специально выделил красной линей процесс подключения протокола. Если вы удалите этот код — компилятор сразу начнет ругаться на строку, в которой вы устанавливаете значение делегата созданному протоколу:

Вторая распространенная ошибка — это просто забыть указать протоколу делегат. То есть пропустить строку myProtocol.delegate = self;

Некоторым из вас может показаться бессмысленным использование протоколов. Кажеться, что так же как мы импортируем MyProtocolв класс MyDelegateViewController, мы можем сделать то же самое с MyProtocol (в MyProtocol импортироватьMyDelegateViewController). Но дело в том, что одновременно это сделать нельзя. А в случае с контроллером навигации это вообще может не работать.

Исходный код этого проекта можно скачать здесь.

В этом примере можно посмотреть как работает механизм делегирования в связки с интерфейсом.

Comments are closed.