可可回调设计:最佳实践

Aku*_*ete 5 cocoa objective-c callback

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

当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
Run Code Online (Sandbox Code Playgroud)

对于选项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
Run Code Online (Sandbox Code Playgroud)

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

回调Option2使用起来很麻烦,并且使用起来不自然.此外,如果协议被扩展,则需要修改每个呼叫.

对每个进程使用特定协议的回调Option1似乎是最具可读性和可扩展性的方法,但是我想知道为每个函数创建一个新协议是否过于冗长(假设给定对象有10个以上的"长操作",那么10不同的协议).

在实施此类设计时,其他人会得出什么结论?

- 编辑: 回复Dave DeLong的回答

我有三个类具有"长操作",每个类中的操作或类之间的操作都非常相关.一些是网络资源请求,另一些是长处理请求.

--edit: 旁注,我似乎有一个问题,我无法为具有多个参数的消息调用运行循环选择器.这是设计限制还是有办法解决这个问题?

例如,我有一个消息,如 - (id)someMessage:(id)value1 otherData:(id)value2 moreData:(id)value3

runLoop队列的performSelector函数不支持这样的选择器.

Tom*_*ing 5

我选择longRunningProcess2longRunningProcess3不仅仅是因为如果你能看到协议上的方法声明,而不是依靠文档来计算回调方法的参数,那么它就更容易理解了.

我想补充一点,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

你会像这样调用这个方法:

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