2010-07-03 131 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