2010-07-03 111 views
5

我正在为可可应用程序编写中间件,并且正在讨论如何为许多长时间运行的进程设计回调。可可回调设计:最佳实践

当UI将调用其执行很长一段时间的函数,它需要提供一个委托,以允许至少:

  • 成功(有返回值)失败的
  • 报告的报告(错误值)
  • 进展的报告(已完成,预计总)

我已经在过去尝试了一些技巧,下面

所示
@interface MyClass { 
} 

//Callback Option1, delgate conforming to protocol 
-(void) longRunningProcess2:(id<CallbackProtocol>) delegate; 

//Callback Option2, provide a delegate and allow it to provide selectors to callback 
-(void) longRunningProcess3:(id) delegate success:(SEL) s1 failure:(SEL) s2 progress:(SEL) s3 
@end 

对于选项1,问题是如何短语委托响应。我认为第一种方式是(函数名称是最小的为简单起见)

//have a generic callback protocol for every function 
@protocol CallbackProtocolGeneric 
-(void) success:(id) returnValue; 
-(void) failure:(NSError*) error; 
@optional 
-(void) progress:(NSInteger) completed of:(NSInteger) total; 
@end 

//have a separate protocol for every function 
@protocol CallbackProtocolWithObjectAForOperation1 
-(void) objectA:(ObjectA*) objectA operation1SucceedWithValue:(ReturnObject*) value; 
-(void) objectA:(ObjectA*) objectA operation1FailedWithError:(NSError*) error; 
@optional 
-(void) objectA:(ObjectA*) objectA operation1didProgress:(NSInteger) completed of:(NSInteger) total; 
@end 

以我的经验, 回调选项1使用通用协议很难因为使用如果一个类想成为一个回调多个操作时,它无法区分它正在接收哪个回调。

回调选项2使用起来很麻烦,感觉不太自然。另外如果协议被扩展,则需要修改每个呼叫。

使用特定协议对于每个过程回拨选项1似乎是最可读的和可扩展的方法,但是不知做一个新的协议为每一个功能是太冗长(说一个给定的对象具有10+这种“长期操作”,然后是10种不同的协议)。

有其他人来执行这样的设计时,什么样的结论?

--edit: 在答复戴夫德隆的回答

我有一个具有“长作业”,每个类或类之间的操作的不真正涉及三类。一些是网络资源请求,另一些则是长时间处理请求。

- 编辑: 一个方面,我似乎有一个问题,我不能调用具有多个参数的消息的运行循环选择器。这是一个设计限制还是有解决方法?

例如,我有一条消息,如 - (ID)someMessage:(ID)值1 otherData:(ID)数值2 MOREDATA:(ID)VALUE3

哪个队列runLoop的不支持这种选择的performSelector功能。

回答

5

我选择了longRunningProcess2而不是longRunningProcess3,只是因为它更容易理解您是否可以在协议上看到方法声明,而不是依靠文档来确定回调方法参数。

我想补充一点,Apple在10.6的新API中使用了回调块,如果您不支持10.5或更早版本,则可以为您提供另一个选项。

块的做法是这样的:

-(void) longRunningProcessWithSuccessHandler:(void(^)(ReturnObject* value))successHandler 
           errorHandler:(void(^)(NSError* error))errorHandler 
          progressHandler:(void(^)(NSInteger completed, NSInteger total))progressHandler; 
{ 
    NSInteger totalItems = 10; 
    NSInteger item = 0; 
    for(item = 0; item < totalItems; ++item){ 
     [self processItem:item]; 
     progressHandler(item, totalItems); 
    } 

    BOOL wasSuccessful = ?; 
    if(wasSuccessful){ 
     ReturnObject* value = ?; 
     successHandler(value); 
    } else { 
     NSError* error = ?; 
     errorHandler(error); 
    } 
} 

而且你会调用该方法是这样的:

[SomeObj longRunningProcessWithSuccessHandler:^(ReturnObject* value) { [self showReturnObject:value]; } 
           errorHandler:^(NSError* error){ [self presentError:error]; } 
           progressHandler:^(NSInteger completed, NSInteger total) { [self updateProgressToPercent:(double)completed/total]; }]; 
+0

我同意明确指定回调,尽管冗长,会导致更好的可读性。我还没有读入'块',但他们看起来比选择器更加灵活。我总是觉得重载一个参数/两个参数执行选择器功能,其中一个黑客和一个糟糕的设计结果。 – Akusete 2010-07-03 07:56:17

3

我会用一个单一的协议路由,类似于您CallbackProtocolGeneric选项去,但我会展开更喜欢:

- (void) operation:(id)operation didFinishWithReturnValue:(id)returnValue; 
- (void) operation:(id)operation didFailWithError:(NSError *)error; 
- (void) operation:(id)operation hasCompleted:(NSInteger)progress ofTotal:(NSInteger)total; 

所以它就像选项1中,你有一个单一的协议,但是像选项2那样,你传递的是更多信息。如果必要的话,你可以用像进一步扩大这样的:

- (void) operation:(id)operation didFinishStep:(NSInteger)stepNumber withReturnValue:(id)returnValue; 
- (void) operation:(id)operation didFailStep:(NSInteger)stepNumber withError:(NSError *)error; 
- (void) operation:(id)operation step:(NSInteger)step hasCompleted:(NSInteger)progress ofTotal:(NSInteger)total; 

的“台阶”参数可以是一些值,表明其中的“10+长期作战”的,这个特定的对象正在。

当然,这个建议是非常通用的,因为你的问题也是相当无效的具体信息,但这可能是我去的方向(不知道更多)。

+0

+1这类似于如何Cocoa类经常工作。例如,NSTableViewDelegate协议方法总是提供引起回调作为参数的表视图。 – 2010-07-03 06:43:41

+0

+1,谢谢。在我的问题中添加了更多具体信息。 – Akusete 2010-07-03 07:13:15

+0

出于某种原因,我可以看到这种方法迫使我在委托实现上做出switch语句,这似乎是错误的。特别是如果这些操作完全不相关。 – Akusete 2010-07-03 07:22:49