Как мне справиться с необходимостью нескольких обратных вызовов для одного и того же делегата в Objective-C? - PullRequest
4 голосов
/ 03 февраля 2010

Я создал библиотеку, которая может загружать данные JSON, которые затем помещаются в NSDictionary. Я обертываю этот класс простым движком Twitter, который позволяет мне перетягивать шкалу времени моих друзей, публиковать обновления и публиковать обновления с моим местоположением GPS. Из моего ограниченного опыта работы с Objective-C способ связать все - с делегированием. Я установил свойство делегата, которое вызывает асинхронный результат для селектора или сигнатуры метода. Я даже могу создать дополнительный или обязательный интерфейс для делегата, который позволит Xcode немного помочь мне с реализацией делегата. Чтобы узнать об использовании делегатов в Objective-C, я создал этот простой проект.

http://www.smallsharptools.com/downloads/ObjC/Delegates.zip

Он определяет класс Worker, который позволяет вам инициализировать класс делегатом. Когда работа завершена с помощью метода doWork, он ищет сигнатуру метода в делегате, чтобы отправить ему сообщение обратно. Используется следующий код.

if([[self delegate] respondsToSelector:@selector(workFinished:)]) {
    NSString *msg = @"That's it? Easy!";
    [[self delegate] workFinished:msg];
}

Ищет workFinished: метод для возврата сообщения. Я объявил подпись этого метода как необязательный интерфейс со следующим кодом в заголовке Worker.h.

  @protocol WorkerNotifications
   @optional
   - (void) workFinished: (NSString *) msg;
   @end

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

Обычно с таким языком, как JavaScript, Java или C #, я бы создал встроенное замыкание или анонимный класс, который имел бы доступ к начальному контексту, но в настоящее время это невозможно с Objective-C на iPhone. Я обнаружил, что этот вопрос уже задавался и отвечал на StackOverflow.

Реализация анонимного делегата в Objective-C?

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

CMTwitterEngine *engine = [[CMTwitterEngine alloc] initWithDelegate:self];
[engine setSendUpdateFinished:@selector(sendUpdateFinished:)];
[engine setSendUpdateFailed:@selector(sendUpdateFailed:)];
[engine setParsingSendUpdateFailed:@selector(parsingSendUpdateFailed:)];
[engine setUsername:TWITTER_USERNAME pass:TWITTER_PASSWORD];
[engine sendUpdate:statusUpdateText.text];

Этот код сначала инициализирует ссылку на двигатель с помощью self в качестве делегата. Чтобы прикрепить обратные вызовы, я отправляю селекторы, которые у меня изначально были в сигнатуре метода sendUpdate, но вызовы методов стали довольно длинными. Я решил просто установить свойства селекторов. Это все работает, но я не уверен, что мне нравится, как это работает, поскольку это только частично решает мою проблему.

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

- (void)sendUpdateFinished:(NSDictionary *)dictionary {
    if (self.sendUpdateFinished != nil) {
        [self.delegate performSelector:self.sendUpdateFinished withObject:dictionary];
    }
}

Я могу передать сообщение о состоянии, чтобы отправить его как обновление Twitter, но у меня все еще нет контекста исходящего вызова. Что если я хочу вызвать sendUpdate более одного раза и первый асинхронный вызов все еще выполняется? А что, если второй вызов заканчивается первым? Они оба будут иметь себя в качестве делегата, поэтому мне придется либо как-то отслеживать контекст, либо передавать их другому селектору, чтобы различать их, что также не удовлетворяет мои потребности. Что произойдет, если у меня будет 3, 4 или 5 асинхронных вызовов? Мне нужно знать, какие из них были успешно отправлены и когда они завершены.

Похоже, единственный способ, которым я могу сделать все это, это создать класс, который содержит все свойства, необходимые для контекста, сделать так, чтобы этот класс выступал в качестве делегата для вызова асинхронного метода Twitter, а затем отчитывался в родительский класс, который, вероятно, UIViewController. Я бы выбрал этот подход, но я еще не читал об этом подходе и не видел ни одного примера кода, который делает это.

Что бы вы сделали? Как бы вы обрабатывали несколько выходящих асинхронных вызовов, которые могли бы заканчиваться в другом порядке, чем выход, и затем обрабатывали бы их с контекстом после завершения?

Ответы [ 3 ]

3 голосов
/ 03 февраля 2010

Я думаю, что ваша ситуация - отличное место для использования NSNotificationCenter

http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html

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

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

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

  1. У NSNotifications есть определенное вами имя, которое является просто строкой NSString. Уведомления могут быть опубликованы по имени, а объекты регистрируются для получения уведомлений по имени.

  2. Когда публикуется уведомление, может быть предоставлен объект NotificationSender и / или словарь userInfo. NotificationSender - это прямой способ определить, кто разместил данное уведомление, когда оно обрабатывается получателем. UserInfo - это NSDictionary, который можно использовать для предоставления дополнительной контекстной информации вместе с уведомлением.

Таким образом, вместо того, чтобы заставлять всех работников переходить к неформальному протоколу и возиться с вызывающими методами стиля отражения - во время выполнения вы просто регистрируете экземпляры работников с помощью NSNotificationCenter. Обычно регистрация в NSNotificationCenter выполняется в методе init каждого рабочего класса. Экземпляры каждого типа работников затем обычно устанавливаются как «высушенные замораживанием» объекты в NIB или могут быть программно созданы в делегате приложения, чтобы они были зарегистрированы в центре уведомлений на ранних этапах жизни приложения.

Когда происходит , вы отправляете NSNotification в NSNotificationCenter (который по сути является одноэлементным), а затем все остальное, которое зарегистрировалось для получения этого конкретного типа уведомления, будет иметь метод, который был указан для обработать этот тип уведомления. После завершения эти методы могут затем вызвать общий метод для отправителя (полученный с помощью объектного метода NSNotification), чтобы сообщить отправителю, что они завершили свою работу.

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

1 голос
/ 03 февраля 2010

Следует учитывать, что вместо этого следует использовать уведомления.Упрощает код, связывает вещи менее плотно.

...