Делегат Objective-C - это объект, который был назначен свойству delegate
другого объекта. Чтобы создать его, вы просто определяете класс, который реализует интересующие вас методы делегата, и помечаете этот класс как реализующий протокол делегата.
Например, предположим, у вас есть UIWebView
. Если вы хотите реализовать метод его делегата webViewDidStartLoad:
, вы можете создать такой класс:
@interface MyClass<UIWebViewDelegate>
// ...
@end
@implementation MyClass
- (void)webViewDidStartLoad:(UIWebView *)webView {
// ...
}
@end
Затем вы можете создать экземпляр MyClass и назначить его делегатом веб-представления:
MyClass *instanceOfMyClass = [[MyClass alloc] init];
myWebView.delegate = instanceOfMyClass;
На стороне UIWebView
он, вероятно, имеет код, подобный этому, чтобы увидеть, отвечает ли делегат на сообщение webViewDidStartLoad:
, используя respondsToSelector:
, и при необходимости отправляет его.
if([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[self.delegate webViewDidStartLoad:self];
}
Само свойство делегата обычно объявляется weak
(в ARC) или assign
(до ARC), чтобы избежать циклов сохранения, поскольку делегат объекта часто содержит строгую ссылку на этот объект. (Например, контроллер представления часто является делегатом представления, которое он содержит.)
Создание делегатов для ваших классов
Чтобы определить своих собственных делегатов, вам нужно где-то объявить их методы, как описано в Документах Apple по протоколам . Вы обычно объявляете официальный протокол. Объявление, перефразированное из UIWebView.h, будет выглядеть так:
@protocol UIWebViewDelegate <NSObject>
@optional
- (void)webViewDidStartLoad:(UIWebView *)webView;
// ... other methods here
@end
Это аналог интерфейса или абстрактного базового класса, поскольку он создает специальный тип для вашего делегата, UIWebViewDelegate
в данном случае. Делегатам-разработчикам придется принять этот протокол:
@interface MyClass <UIWebViewDelegate>
// ...
@end
А затем реализовать методы в протоколе. Для методов, объявленных в протоколе как @optional
(как и большинство методов делегатов), вам необходимо проверить с помощью -respondsToSelector:
перед вызовом определенного метода для него.
Нейминг
Методы делегирования обычно именуются, начиная с имени класса делегирования, и принимают делегирующий объект в качестве первого параметра. Они также часто используют волю, следует или сделали. Так, например, webViewDidStartLoad:
(первый параметр - веб-представление), а не loadStarted
(без параметров).
Оптимизация скорости
Вместо того, чтобы проверять, отвечает ли делегат на селектор каждый раз, когда мы хотим отправить его, вы можете кэшировать эту информацию, когда установлены делегаты. Один очень простой способ сделать это - использовать битовое поле, как показано ниже:
@protocol SomethingDelegate <NSObject>
@optional
- (void)something:(id)something didFinishLoadingItem:(id)item;
- (void)something:(id)something didFailWithError:(NSError *)error;
@end
@interface Something : NSObject
@property (nonatomic, weak) id <SomethingDelegate> delegate;
@end
@implementation Something {
struct {
unsigned int didFinishLoadingItem:1;
unsigned int didFailWithError:1;
} delegateRespondsTo;
}
@synthesize delegate;
- (void)setDelegate:(id <SomethingDelegate>)aDelegate {
if (delegate != aDelegate) {
delegate = aDelegate;
delegateRespondsTo.didFinishLoadingItem = [delegate respondsToSelector:@selector(something:didFinishLoadingItem:)];
delegateRespondsTo.didFailWithError = [delegate respondsToSelector:@selector(something:didFailWithError:)];
}
}
@end
Затем в теле мы можем проверить, что наш делегат обрабатывает сообщения, обращаясь к нашей delegateRespondsTo
структуре, а не посылая -respondsToSelector:
снова и снова.
Неофициальные делегаты
До появления протоколов было принято использовать категорию на NSObject
для объявления методов, которые может реализовать делегат. Например, CALayer
все еще делает это:
@interface NSObject(CALayerDelegate)
- (void)displayLayer:(CALayer *)layer;
// ... other methods here
@end
Это, по сути, говорит компилятору, что любой объект может реализовать displayLayer:
.
Затем вы бы использовали тот же подход -respondsToSelector:
, как описано выше, для вызова этого метода. Делегаты просто реализуют этот метод и присваивают свойство delegate
, и все (нет никаких заявлений о том, что вы соответствуете протоколу). Этот метод распространен в библиотеках Apple, но новый код должен использовать более современный протоколный подход, описанный выше, так как этот подход загрязняет NSObject
(что делает автозаполнение менее полезным) и затрудняет компилятору предупреждение о опечатках и похожих ошибках.