Работа с переменными назначениями и асинхронными запросами - PullRequest
3 голосов
/ 15 декабря 2008

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

Контроллер представления может сделать что-то вроде следующего:

users = [MySingleton sharedInstance].users;

MySingleton затем переопределит синтезированный пользовательский геттер и увидит, установлен ли он. Если он не установлен, он будет обращаться к диспетчеру соединений (оболочка для NSURLConnection и его методов делегата), который запускает асинхронный запрос, и именно здесь начинаются проблемы. Я не могу гарантировать, когда будут доступны «пользователи». Я мог бы изменить запрос на синхронный, но это напрямую повлияет на взаимодействие с пользователем, особенно в мобильной среде, где пропускная способность уже ограничена.

Мне нужно, чтобы в какой-то момент в моем геттере работал какой-то код блокировки / синхронизации, который не возвращает пользователей, пока он не станет доступен или равен нулю.

Как только NSURLConnection располагает доступными данными, ему нужно что-то / где-то вызвать с помощью объекта ответа и сообщить получателю, что данные доступны ... независимо от того, были они неудачными или успешными.

Любые предложения по обработке этого?

Ответы [ 3 ]

3 голосов
/ 16 декабря 2008

Я решил эту проблему несколькими способами в разных приложениях.

Одним из решений является передача объекта и селектора для уведомления, например:

- (id)getUsersAndNotifyObject:(id)object selector:(SEL)selector

Однако это нарушает хорошее поведение свойства. Если вы хотите сохранить методы как свойства, попросите их немедленно вернуться с кэшированными данными или с нулем. Если вам нужно выйти в сеть, сделайте это асинхронно, а затем сообщите остальной части приложения, что данные изменились через KVO или NSNotificationCenter. (Привязки какао могут быть на Mac, но на iPhone их нет).

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

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

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

Метод КВО:

// register observer first so you don't miss an update
[[MySingleton sharedInstance] addObserver:self
                               forKeyPath:@"users"
                                  options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
                                  context:&kvo_users_context];
 users = [MySingleton sharedInstance].users;

 // implement appropriate observeValueForKeyPath:ofObject:change:context: method

NSNotification Способ:

 [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(sharedDataChanged:)
                                              name:MySingletonDataUpdatedNotification
                                            object:[MySingletonDataUpdatedNotification sharedInstance]];
 users = [MySingleton sharedInstance].users;

 // implement appropriate sharedDataChanged: method
1 голос
/ 16 декабря 2008

Здесь вы можете использовать шаблон делегата или шаблон уведомления.

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

Просто помните: если у вас есть какие-либо проблемы с расой в вашем приложении, ваша архитектура, вероятно, совершенно неверна.

0 голосов
/ 02 июля 2009

Мне потребовалось некоторое время, чтобы понять, как лучше всего справиться с этим типичным заданием; оказывается, что ключ в дизайне многих собственных API-интерфейсов Cocoa и CocoaTouch: делегирование .

Причина, по которой многие API-интерфейсы Cocoa используют делегирование, заключается в том, что он очень хорошо согласуется с асинхронной природой многих приложений с графическим интерфейсом.

Кажется совершенно нормальным хотеть сделать что-то вроде:

users = [MyDataFactory getUsers];

Кроме того, как вы указали, вы не знаете, когда закончится метод getUsers. Теперь есть несколько легких решений для этого; amrox упомянул некоторые из них в своем посте выше (лично я бы сказал, что уведомления не очень хорошо подходят, но объект: селектор: шаблон является разумным), но если вы много делаете такого рода вещи шаблон делегирования имеет тенденцию приводить к более элегантному решению.

Я попытаюсь объяснить на примере, как я делаю вещи в своем приложении.

Допустим, у нас есть класс домена, Recipe. Рецепты взяты из веб-службы. Обычно у меня есть серия репозитория классов, по одному для каждой сущности в моей модели. Класс репозитория отвечает за выборку данных, необходимых для сущности (или их коллекции), использование этих данных для конструирования объектов, а затем передачу этих объектов во что-то другое для их использования (обычно это контроллер или источник данных). ).

Мой RecipeRepository интерфейс может выглядеть примерно так:

@interface RecipeRepository {}
- (void)initWithDelegate:(id)aDelegate;
- (void)findAllRecipes;
- (void)findRecipeById:(NSUInteger)anId;
@end

Я бы тогда определил протокол для моего делегата; Теперь это можно сделать как неофициальный или формальный протокол, есть плюсы и минусы каждого подхода, которые не имеют отношения к этому ответу. Я пойду с формальным подходом:

@protocol RepositoryDelegateProtocol
- (void)repository:(id)repository didRetrieveEntityCollection:(NSArray *)collection;
- (void)repository:(id)repository didRetrieveEntity:(id)entity;
@end

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

Теперь, чтобы использовать это в контроллере, например, где вы предыдущий сделал бы что-то вроде:

- (void)viewDidLoad 
{
  self.users = [MySingleton getUsers];
  [self.view setNeedsDisplay];
}

Вы бы сделали что-то вроде этого:

- (void)viewDidLoad 
{
  if(self.repository == nil) { // just some simple lazy loading, we only need one repository instance
    self.repository = [[[RecipeRepository alloc] initWithDelegate:self] autorelease];
  }
  [self.repository findAllRecipes];
}

- (void)repository:(id)repository didRetrieveEntityCollection:(NSArray *)collection;
{
  self.users = collection;
  [self.view setNeedsDisplay];
}

Вы можете даже расширить это, чтобы отобразить какое-то уведомление о «загрузке» с помощью дополнительного метода делегата:

@protocol RepositoryDelegateProtocol
- (void)repositoryWillLoadEntities:(id)repository;
@end

// in your controller

- (void)repositoryWillLoadEntities:(id)repository;
{
  [self showLoadingView]; // etc.
}

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

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

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

Во всяком случае, это мой первый ответ на SO, я надеюсь, что это было полезно.

...