Мне потребовалось некоторое время, чтобы понять, как лучше всего справиться с этим типичным заданием; оказывается, что ключ в дизайне многих собственных 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, я надеюсь, что это было полезно.