我花了一段时间才意识到处理这种典型任务的最佳方式是什么;事实证明,线索在于许多 Cocoa 和 CocoaTouch 自己的 API 的设计:委托。
很多 Cocoa 的 API 使用委托的原因是因为它非常适合许多 GUI 应用程序的异步特性。
想要做一些类似的事情似乎是完全正常的:
users = [MyDataFactory getUsers];
除了,正如您所指出的,您不知道该getUsers
方法何时完成。现在,有一些轻量级的解决方案;amrox在上面的帖子中提到了一些(我个人认为通知不太合适,但 object:selector: 模式是合理的),但如果你经常做这种事情,委托模式往往会产生一个更优雅的解决方案。
我将尝试通过一个示例来解释我如何在我的应用程序中做事。
假设我们有一个域类,Recipe
. 食谱是从 Web 服务中获取的。我通常有一系列存储库类,一个用于我模型中的每个实体。存储库类的职责是获取实体(或它们的集合)所需的数据,使用该数据构造对象,然后将这些对象传递给其他对象以使用它们(通常是控制器或数据源)。
我的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 的第一个回答,希望对您有所帮助。