Делегаты против Уведомления в iPhoneOS - PullRequest
33 голосов
/ 10 февраля 2010

Я пытаюсь вызвать метод в моем корневом контроллере представления из дочернего контроллера представления так, чтобы, когда я изменяю свои параметры, они автоматически обновляли корневое представление, которое, в свою очередь, обновляет несколько других контроллеров представления. Во второй части я использовал уведомления, но для этого сначала я пытаюсь использовать делегата, потому что это (так мне поверили) хорошая практика программирования. У меня проблемы с его настройкой, и я знаю, что могу легко настроить другое уведомление для выполнения этой работы. Должен ли я продолжать пытаться реализовать делегата или просто использовать уведомление?

Ответы [ 7 ]

58 голосов
/ 10 февраля 2010

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

Некоторые преимущества делегирования:

  • Связь между делегирующим объектом и делегатом проясняется, особенно если реализация делегата обязательна.
  • Если от делегата к делегату необходимо передать более одного типа сообщения, делегирование может сделать это более понятным, указав один метод делегата на сообщение. Для уведомлений вы можете использовать несколько имен уведомлений, но все уведомления заканчиваются одним и тем же методом на стороне наблюдателя (возможно, требующий неприятного оператора switch).

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

Реализация шаблона делегата проста:

  1. В вашем ChildViewController.h объявите протокол делегата, который делегат должен реализовать позже:

    @protocol ChildViewControllerDelegate <NSObject>
    @optional
    - (void)viewControllerDidChange:(ChildViewController *)controller;
    @end
    
  2. В верхней части файла создайте переменную экземпляра для хранения указателя на делегата в вашем ChildViewController:

    @protocol ChildViewControllerDelegate;
    @interface ChildViewController : UIViewController {
        id <ChildViewControllerDelegate> delegate;
        ...
    }
    @property (assign) id <ChildViewControllerDelegate> delegate;
    ...
    @end
    
  3. В RootViewController.h приведите ваш класс в соответствие с протоколом делегата:

    @interface RootViewController : UIViewController <ChildViewControllerDelegate> {
    ...
    
  4. В реализации RootViewController реализовать метод делегата. Кроме того, при создании экземпляра ChildViewController необходимо назначить делегата.

    @implement RootViewController
    ...
    // in some method:
    ChildViewController *controller = [[ChildViewController alloc] initWithNibName:...
    controller.delegate = self;
    ...
    - (void)viewControllerDidChange:(ChildViewController *)controller {
        NSLog(@"Delegate method was called.");
    }
    ...
    
  5. В реализации ChildViewController вызовите метод делегата в соответствующее время:

    @implementation ChildViewController
    ...
    // in some method:
    if ([self.delegate respondsToSelector:@selector(viewControllerDidChange:)]) {
        [self.delegate viewControllerDidChange:self];
    }
    ...
    

Вот и все. (Примечание: я написал это по памяти, поэтому, возможно, в нем есть некоторые опечатки / ошибки.)

25 голосов
/ 19 августа 2010

Я хотел бы добавить:

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

11 голосов
/ 10 февраля 2010

Как правило, если вам нужно обновить пользовательский интерфейс на основе изменения данных в модели, контроллеры представлений будут наблюдать соответствующие данные модели и обновлять свои представления при получении уведомления об изменениях.

Я считаю делегирование более формальным и похожим на то различие, которое Питер Хоси недавно разделил:

Разница в том, что делегирование для одного (и двунаправленного) связь, тогда как уведомления для многих, однонаправленных связи.

Кроме того, я обнаружил, что (полностью) обновление представления в viewWillAppear: работает нормально (но это не лучшее решение, в котором важна производительность).

6 голосов
/ 15 апреля 2014

Уведомления могут значительно усложнить поведение вашей программы во время выполнения. Думайте об этом как о goto с несколькими направлениями. Порядок этих направлений не определен. Если вы когда-нибудь потерпели крах, информация о трассировке стека мало.

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

Стоит изучить шаблон делегата в iOS. Делегаты дают вам полную трассировку стека при отладке. Они приводят к значительно более простому поведению во время выполнения, в то же время достигая цели отделения ваших объектов.

2 голосов
/ 10 февраля 2010

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

Я всегда использую формальное объявление протокола. Это немного более логично, на мой взгляд, и очень ясно в коде. Я предлагаю использовать UIView, чтобы изменить ваши параметры вместо контроллера. Я всегда использую один основной контроллер и имею множество подклассов UIViews, которыми может управлять один контроллер. (Однако вы можете изменить следующий код для контроллера, , если вам действительно нужен контроллер вместо обычного представления . ) В файле заголовка дочернего представления, сделайте так:

// ChildView.h
#import <UIKit/UIKit.h>

@protocol ChildViewDelegate; // tells the compiler that there will be a protocol definition later

@interface ChildViewController : UIView {
    id <ChildViewDelegate> delegate;
    // more stuff
}

// properties and class/instance methods

@end

@protocol ChildViewDelegate // this is the formal definition

- (void)childView:(ChildView *)c willDismissWithButtonIndex:(NSInteger)i; // change the part after (ChildView *)c to reflect the chosen options

@end

Метод между @protocol и вторым @end может быть вызван где-нибудь в реализации ChildView, и тогда ваш корневой контроллер представления может быть делегатом, который получает «уведомление».

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

// ChildView.m
#import "ChildView.h"

@implementation ChildView

- (id)initWithDelegate:(id<ChildViewDelegate>)del { // make this whatever you want
    if (self = [super initWithFrame:CGRect(0, 0, 50, 50)]) { // if frame is a parameter for the init method, you can make that here, your choice
        delegate = del; // this defines what class listens to the 'notification'
    }
    return self;
}

// other methods

// example: a method that will remove the subview

- (void)dismiss {
    // tell the delegate (listener) that you're about to dismiss this view
    [delegate childView:self willDismissWithButtonIndex:3];
    [self removeFromSuperView];
}

@end

Тогда файл .h контроллера корневого представления будет содержать следующий код:

// RootViewController.h
#import "ChildView.h"

@interface RootViewController : UIViewController <ChildViewDelegate> {
    // stuff
}

// stuff

@end

И файл реализации будет реализовывать метод, определенный в протоколе в ChildView.h, потому что он будет запускаться, когда ChildView вызывает его запуск. В этом методе укажите, что происходит, когда вы получаете уведомление.

0 голосов
/ 10 февраля 2010

Действительно ли ваш корневой контроллер должен знать об изменениях или только о подпредставлениях?

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

0 голосов
/ 10 февраля 2010

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

Ваша модель данных должна быть независимым объектом, к которому имеют доступ все ваши контроллеры представления. (Ленивый способ - это оставить его как атрибут делегата приложения.) Когда пользователь вносит изменения в представление A, контроллер представления A записывает это изменение в модель данных. Затем всякий раз, когда открывается вид от B до Z, их контроллеры считывают модель данных и настраивают представления соответствующим образом.

Таким образом, ни представления, ни их контроллеры не должны знать друг о друге, и все изменения происходят в одном центральном объекте, поэтому их легко отслеживать.

...