Как мне создать делегатов в Objective-C? - PullRequest
723 голосов
/ 09 марта 2009

Я знаю, как работают делегаты, и знаю, как их использовать.

Но как мне их создать?

Ответы [ 19 ]

871 голосов
/ 09 марта 2009

Делегат 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 (что делает автозаполнение менее полезным) и затрудняет компилятору предупреждение о опечатках и похожих ошибках.

375 голосов
/ 30 сентября 2012

Одобренный ответ - это здорово, но если вы ищете 1-минутный ответ, попробуйте это:

Файл MyClass.h должен выглядеть следующим образом (добавьте строки делегатов с комментариями!)

#import <BlaClass/BlaClass.h>

@class MyClass;             //define class, so protocol can see MyClass
@protocol MyClassDelegate <NSObject>   //define delegate protocol
    - (void) myClassDelegateMethod: (MyClass *) sender;  //define delegate method to be implemented within another class
@end //end protocol

@interface MyClass : NSObject {
}
@property (nonatomic, weak) id <MyClassDelegate> delegate; //define MyClassDelegate as delegate

@end

Файл MyClass.m должен выглядеть следующим образом

#import "MyClass.h"
@implementation MyClass 
@synthesize delegate; //synthesise  MyClassDelegate delegate

- (void) myMethodToDoStuff {
    [self.delegate myClassDelegateMethod:self]; //this will call the method implemented in your other class    
}

@end

Чтобы использовать ваш делегат в другом классе (в данном случае UIViewController называется MyVC) MyVC.h:

#import "MyClass.h"
@interface MyVC:UIViewController <MyClassDelegate> { //make it a delegate for MyClassDelegate
}

MyVC.m:

myClass.delegate = self;          //set its delegate to self somewhere

Реализация метода делегата

- (void) myClassDelegateMethod: (MyClass *) sender {
    NSLog(@"Delegates are great!");
}
18 голосов
/ 05 мая 2010

При использовании метода формального протокола для создания поддержки делегатов я обнаружил, что вы можете обеспечить правильную проверку типов (хотя и во время выполнения, а не во время компиляции), добавив что-то вроде:

if (![delegate conformsToProtocol:@protocol(MyDelegate)]) {
    [NSException raise:@"MyDelegate Exception"
                format:@"Parameter does not conform to MyDelegate protocol at line %d", (int)__LINE__];
}

в вашем коде доступа делегата (setDelegate). Это помогает минимизировать ошибки.

17 голосов
/ 10 марта 2009

Может быть, это больше похоже на то, чего вам не хватает:

Если вы исходите из точки зрения, подобной C ++, делегатам нужно немного привыкнуть, но в основном «они просто работают».

Способ, которым это работает, заключается в том, что вы устанавливаете некоторый объект, который вы написали в качестве делегата для NSWindow, но у вашего объекта есть реализации (методы) только для одного или нескольких из множества возможных методов делегата. Итак, что-то происходит, и NSWindow хочет вызвать ваш объект - он просто использует метод respondsToSelector Objective-c, чтобы определить, хочет ли ваш объект вызвать этот метод, а затем вызывает его. Вот как работает target-c - методы ищутся по требованию.

Совершенно тривиально сделать это с вашими собственными объектами, ничего особенного не происходит, например, вы можете иметь NSArray из 27 объектов, различных типов объектов, только 18 из них имеют метод -(void)setToBue; Остальные 9 нет. Итак, чтобы позвонить setToBlue на все 18, которые нуждаются в этом, что-то вроде этого:

for (id anObject in myArray)
{
  if ([anObject respondsToSelector:@selector(@"setToBlue")])
     [anObject setToBlue]; 
}

Другая особенность делегатов в том, что они не сохраняются, поэтому вам всегда нужно установить делегата на nil в вашем методе MyClass dealloc.

17 голосов
/ 27 февраля 2013

Пожалуйста! посмотрите ниже простое пошаговое руководство, чтобы понять, как делегаты работают в iOS.

Делегат в iOS

Я создал два ViewController (для отправки данных от одного к другому)

  1. FirstViewController реализует делегат (который предоставляет данные).
  2. SecondViewController объявляет делегата (который будет получать данные).
15 голосов
/ 04 августа 2013

В соответствии с рекомендациями Apple, рекомендуется, чтобы делегат (который по определению является протоколом) соответствовал протоколу NSObject.

@protocol MyDelegate <NSObject>
    ...
@end

& для создания в вашем делегате дополнительных методов (т. Е. Методов, которые не обязательно должны быть реализованы), вы можете использовать аннотацию @optional, например:

@protocol MyDelegate <NSObject>
    ...
    ...
      // Declaration for Methods that 'must' be implemented'
    ...
    ...
    @optional
    ...
      // Declaration for Methods that 'need not necessarily' be implemented by the class conforming to your delegate
    ...
@end

Таким образом, при использовании методов, которые вы указали как необязательные, вам необходимо (в вашем классе) проверить с помощью respondsToSelector, действительно ли представление (соответствующее вашему делегату) действительно реализовало ваш дополнительный метод (ы) или нет.

10 голосов
/ 19 июня 2013

Я думаю, что все эти ответы имеют большой смысл, когда вы понимаете делегатов. Лично я приехал из страны Си / Си ++ и до этого процедурные языки, такие как Фортран и т. Д., Так что вот мои 2 минуты, чтобы найти аналогичные аналоги в парадигме Си ++.

Если бы я объяснил делегатов программисту на C ++ / Java, я бы сказал

Что такое делегаты? Это статические указатели на классы внутри другого класса. Как только вы назначите указатель, вы можете вызывать функции / методы в этом классе. Следовательно, некоторые функции вашего класса «делегируются» (в мире C ++ - указатель на указатель объекта класса) другому классу.

Что такое протоколы? Концептуально это служит аналогичным назначением для файла заголовка класса, который вы назначаете в качестве класса делегата. Протокол - это явный способ определения того, какие методы должны быть реализованы в классе, указатель которого был задан в качестве делегата внутри класса.

Как я могу сделать что-то подобное в C ++? Если бы вы попытались сделать это в C ++, вы должны определить указатели на классы (объекты) в определении класса, а затем связать их с другими классами, которые предоставят дополнительные функции в качестве делегатов вашему базовому классу. Но эта проводка должна поддерживаться в коде и будет неуклюжей и подверженной ошибкам. Цель C просто предполагает, что программисты не лучше всего поддерживают эту дешифровку, и предоставляет ограничения компилятора для обеспечения чистой реализации.

9 голосов
/ 05 ноября 2015

Swift версия

Делегат - это просто класс, который выполняет некоторую работу для другого класса. Прочитайте следующий код для несколько глупого (но, надеюсь, поучительного) примера Playground, который показывает, как это делается в Swift.

// A protocol is just a list of methods (and/or properties) that must
// be used by any class that adopts the protocol.
protocol OlderSiblingDelegate: class {
    // This protocol only defines one required method
    func getYourNiceOlderSiblingAGlassOfWater() -> String
}

class BossyBigBrother {

    // The delegate is the BossyBigBrother's slave. This position can 
    // be assigned later to whoever is available (and conforms to the 
    // protocol).
    weak var delegate: OlderSiblingDelegate?

    func tellSomebodyToGetMeSomeWater() -> String? {
        // The delegate is optional because there might not be anyone
        // nearby to boss around.
        return delegate?.getYourNiceOlderSiblingAGlassOfWater()
    }
}

// PoorLittleSister conforms to the OlderSiblingDelegate protocol
class PoorLittleSister: OlderSiblingDelegate {

    // This method is repquired by the protocol, but the protocol said
    // nothing about how it needs to be implemented.
    func getYourNiceOlderSiblingAGlassOfWater() -> String {
        return "Go get it yourself!"
    }

}

// initialize the classes
let bigBro = BossyBigBrother()
let lilSis = PoorLittleSister()

// Set the delegate 
// bigBro could boss around anyone who conforms to the 
// OlderSiblingDelegate protocol, but since lilSis is here, 
// she is the unlucky choice.
bigBro.delegate = lilSis

// Because the delegate is set, there is a class to do bigBro's work for him.
// bigBro tells lilSis to get him some water.
if let replyFromLilSis = bigBro.tellSomebodyToGetMeSomeWater() {
    print(replyFromLilSis) // "Go get it yourself!"
}

На практике делегаты часто используются в следующих ситуациях

  1. Когда класс должен передать некоторую информацию другому классу
  2. Когда класс хочет разрешить другому классу настраивать его

Классам не нужно ничего знать друг о друге заранее, за исключением того, что класс делегата соответствует требуемому протоколу.

Я настоятельно рекомендую прочитать следующие две статьи. Они помогли мне понять делегатов даже лучше, чем документация .

8 голосов
/ 08 апреля 2015

Допустим, у вас есть класс, который вы разработали и хотите объявить свойство делегата, чтобы иметь возможность уведомлять его, когда происходит какое-то событие:

@class myClass;

@protocol myClassDelegate <NSObject>

-(void)myClass:(MyClass*)myObject requiredEventHandlerWithParameter:(ParamType*)param;

@optional
-(void)myClass:(MyClass*)myObject optionalEventHandlerWithParameter:(ParamType*)param;

@end


@interface MyClass : NSObject

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

@end

поэтому вы объявляете протокол в MyClass заголовочном файле (или отдельном заголовочном файле) и объявляете обязательные / необязательные обработчики событий, которые ваш делегат должен / должен реализовать, а затем объявляете свойство в MyClass типа (*) 1006 *), что означает любой целевой класс c, соответствующий протоколу MyClassDelegate, вы заметите, что свойство делегата объявлено как слабое, это очень важно для предотвращения сохранения цикла (чаще всего делегат сохраняет экземпляр MyClass поэтому, если вы объявили делегата как сохраняемого, оба они сохранят друг друга, и ни один из них никогда не будет освобожден).

вы также заметите, что методы протокола передают экземпляр MyClass делегату в качестве параметра, это наилучшая практика, если делегат хочет вызвать некоторые методы в экземпляре MyClass, а также помогает, когда делегат объявляет себя как MyClassDelegate для нескольких MyClass экземпляров, например, когда у вас есть несколько UITableView's экземпляров в вашем ViewController, и вы объявляете себя как UITableViewDelegate для всех них.

и внутри вашего MyClass вы уведомляете делегата о объявленных событиях следующим образом:

if([_delegate respondsToSelector:@selector(myClass: requiredEventHandlerWithParameter:)])
{
     [_delegate myClass:self requiredEventHandlerWithParameter:(ParamType*)param];
}

вы сначала проверяете, отвечает ли ваш делегат на метод протокола, который вы собираетесь вызвать, в случае, если делегат не реализует его, а затем произойдет сбой приложения (даже если метод протокола требуется).

8 голосов
/ 17 июля 2013

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

Я с трудом реализую своих делегатов, потому что мне это редко нужно. Я могу иметь ТОЛЬКО ОДИН делегат для объекта делегата. Так что если вы хотите, чтобы ваш делегат для односторонней связи / передачи данных, вам гораздо лучше с уведомлениями.

NSNotification может передавать объекты нескольким получателям, и его очень легко использовать. Это работает так:

Файл MyClass.m должен выглядеть следующим образом

#import "MyClass.h"
@implementation MyClass 

- (void) myMethodToDoStuff {
//this will post a notification with myClassData (NSArray in this case)  in its userInfo dict and self as an object
[[NSNotificationCenter defaultCenter] postNotificationName:@"myClassUpdatedData"
                                                    object:self
                                                  userInfo:[NSDictionary dictionaryWithObject:selectedLocation[@"myClassData"] forKey:@"myClassData"]];
}
@end

Чтобы использовать ваше уведомление в других классах: Добавить класс в качестве наблюдателя:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(otherClassUpdatedItsData:) name:@"myClassUpdatedData" object:nil];

Реализация селектора:

- (void) otherClassUpdatedItsData:(NSNotification *)note {
    NSLog(@"*** Other class updated its data ***");
    MyClass *otherClass = [note object];  //the object itself, you can call back any selector if you want
    NSArray *otherClassData = [note userInfo][@"myClassData"]; //get myClass data object and do whatever you want with it
}

Не забудьте удалить свой класс в качестве наблюдателя, если

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...