2010-07-03 13 views
5

Piszę oprogramowanie pośredniczące do aplikacji kakao i zastanawiam się, jak dokładnie zaprojektować wywołania zwrotne dla wielu długotrwałych procesów.Projektowanie połączenia zwrotnego kakao: najlepsza praktyka

Kiedy UI będzie wywołać funkcję, która wykonuje przez długi czas, to musi zapewnić delegata, aby umożliwić co najmniej:

  • Report of Success (z wartości zwracanej)
  • Raportu Awarii (z wartości błędu)
  • Raport Postępu (zakończona, oczekuje całkowity)

próbowałem kilka technik w przeszłości, poniżej

@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 

W przypadku opcji 1 pytanie brzmi: jak sformułować odpowiedź delegata. Pierwszym sposobem Uważałem to (nazwy funkcji są minimalne dla uproszczenia)

//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 

Z mojego doświadczenia wynika, opcji oddzwaniania 1 użyciu typowego protokołu był trudny w użyciu, ponieważ jeśli klasa chciał być wywołania zwrotnego dla wielu operacji, które nie były w stanie rozróżnić, który oddzwonił.

Opcja oddzwaniania2 była uciążliwa w użyciu i wydawała się nienaturalna w użyciu. Plus, jeśli protokół został przedłużony, wymagałoby modyfikacji każdego połączenia.

oddzwaniania Opcja 1 użyciu konkretny protokół dla każdego procesu wydaje się być sposób najbardziej czytelny i skalowalne, jednak zastanawiam się, czy tworzenia nowego protokołu dla każdej funkcji jest zbyt rozwlekły (Say dany obiekt ma ponad 10 takie "długie operacje", a następnie 10 różnych protokołów).

Jakie konkluzje mają inne osoby wdrażające takie projekty?

--edit: W odpowiedzi Dave Delong za odpowiedź

mam trzy klasy, które mają „” długie operacje, bez operacji w każdej klasie lub między klasami są bardzo podobne. Niektóre to żądania zasobów sieciowych, inne to długie żądania przetwarzania.

--edit: Na marginesie, wydaje mi się, że problem, gdzie nie może powoływać selektorów bieg pętli komunikatów, które mają więcej niż jeden argument. Czy jest to ograniczenie projektu, czy jest jakiś sposób obejścia tego?

Przykładowo mam komunikatów, takich jak - (ID) someMessage (ID) otherData Value1 (ID) wartość2 moreData (ID) VALUE3

Funkcje performSelector odpowiedniej kolejki runLoop nie obsługują te selektory .

Odpowiedz

5

Wybrałem longRunningProcess2 zamiast longRunningProcess3 tylko dlatego, że łatwiej jest zrozumieć, czy można zobaczyć deklaracje metody na protokole, w przeciwieństwie do polegania na dokumentacji, aby dowiedzieć się, jakie są argumenty metody wywołania zwrotnego.

Chciałbym dodać, że Apple używa bloków dla wywołań zwrotnych w API new do 10.6, co daje inną opcję, jeśli nie obsługujesz wersji 10.5 lub wcześniejszej.

Podejście bloków będzie wyglądać następująco:

-(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); 
    } 
} 

I chcesz wywołać metodę tak:

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

Zgadzam się, że jawne nazewnictwo wywołań zwrotnych, chociaż jest pełne, prowadzi do znacznie lepszej czytelności. Nie czytałem jeszcze "bloków", ale wyglądają one znacznie elastyczniej niż selektory. Zawsze czułem, że przeciążony jeden parametr/dwa parametry wykonują funkcje selektora, gdzie hack i wynik złego projektu. – Akusete

3

pójdę z jednym trasie protokół, podobny do opcji CallbackProtocolGeneric, oprócz tego, że bym go rozwinąć się bardziej jak:

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

Tak to jest jak w wariancie 1, że masz jeden protokół, ale jak w opcji 2, w której przekazujesz więcej informacji. W razie potrzeby można rozszerzyć to dalej z czymś takim:

- (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; 

parametr „krok” może być jakaś wartość, która wskazuje, które z długich operacji „10+”, że ten konkretny obiekt robi.

Oczywiście, ta rada jest bardzo ogólna, ponieważ twoje pytanie jest również pozbawione konkretnych informacji, ale jest to prawdopodobnie kierunek, w którym pójdę (nie wiedząc więcej).

+0

+1 Jest to podobne do tego, jak zajęcia kakao często pracują. Na przykład metody protokołu NSTableViewDelegate zawsze udostępniają widok tabeli, który spowodował wywołanie zwrotne jako parametr. –

+0

+1, dzięki. W dodałem bardziej szczegółowe informacje do mojego pytania. – Akusete

+0

Z jakiegoś powodu widzę tę metodę, zmuszając mnie do zmiany oświadczenia dotyczącego implementacji delegatów, co wydaje się błędne. Zwłaszcza jeśli operacje są całkowicie niezależne. – Akusete

Powiązane problemy