我正在寻找一种可靠的设计来处理涉及异步请求的分配。为了进一步澄清,我有一个处理数据管理的类。它是一个单例,包含很多顶级数据,在我的iPhone应用程序中使用。
视图控制器可能会执行以下操作:
users = [MySingleton sharedInstance].users;
然后,MySingleton会覆盖合成用户的getter并查看它是否已设置。如果未设置,它将与Connection Manager(NSURLConnection的包装器及其委托方法)通信,该连接管理器将触发异步请求,这就是问题开始的地方。我无法保证“用户”何时可用。我可以将请求更改为同步,但这将直接影响用户体验,尤其是在带宽有限的移动环境中。
我需要能够在某些时候,在我的getter中继续使用某种锁定/同步代码,这些代码在用户可用或为零之前不会返回用户。
一旦NSURLConnection具有可用数据,它就需要使用响应对象回调某个/某处,并让getter知道数据是否可用..无论是失败还是成功。
有关处理此事的任何建议吗?
答案 0 :(得分:3)
我在不同的应用中解决了这个问题。
一种解决方案是传递一个对象和选择器,以通知如下:
- (id)getUsersAndNotifyObject:(id)object selector:(SEL)selector
然而,这打破了不错的财产行为。如果要将方法保留为属性,请使用缓存数据或nil立即返回它们。如果您需要转到网络,请执行异步操作,然后让应用程序的其余部分知道通过KVO或NSNotificationCenter更改的数据。 (Cocoa Bindings将是Mac上的一个选项,但它们在iPhone上不存在。)
这两种方法非常相似。使用共享实例注册更新,然后询问数据。如果您只处理原始的可观察属性,KVO的重量会稍微减轻,但如果您对几个不同的数据感兴趣,则NSNotification可能会更方便。
使用NSNotification,客户端对象可以注册一种类型的通知,其中包含其userInfo字典中已更改的数据,而不必为您感兴趣的每个关键路径注册obvserver。
NSNotification还允许您比直接KVO更容易地传回失败或其他状态信息。
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 :(得分:1)
您可以在此处使用委托模式或通知模式。
委托会让某个特定对象知道用户何时完成,通知模式会通知任何想要知道的对象。两者都有效,具体取决于您的情况。
请记住:如果您的应用中存在任何竞争问题,那么您的架构可能就错了。
答案 2 :(得分:0)
我花了一段时间才意识到处理这种典型任务的最佳方法是什么;事实证明,许多Cocoa和CocoaTouch自己的API设计的线索是:委托。
Cocoa的许多API使用委托的原因是因为它非常适合许多GUI应用程序的异步性质。
希望按照以下方式做某事似乎是完全正常的:
users = [MyDataFactory getUsers];
除非您指出,否则您不知道getUsers
方法何时结束。现在,有一些轻量级的解决方案; amrox 在上面的帖子中提到了一些(我个人认为通知不是很合适但对象:选择器:模式是合理的),但是如果你正在做这种事情的话委托模式倾向于产生更优雅的解决方案。
我将尝试通过一个示例来解释我如何在我的应用程序中执行操作。
假设我们有一个域类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上的第一个答案,我希望它有所帮助。