2010-07-03 8 views
5

Estoy escribiendo el middleware para una aplicación de cacao, y estoy debatiendo sobre cómo exactamente diseñar callbacks para muchos procesos de larga ejecución.Cocoa Callback Design: Best Practice

Cuando la interfaz de usuario llamará a una función que se ejecuta durante mucho tiempo, es necesario proporcionar un delegado para permitir al menos:

  • Informe de éxito (con valor de retorno)
  • Informe de fallos (con el valor de error)
  • Informe de Progreso (terminado, total esperado)

he intentado algunas técnicas en el pasado, se muestra a continuación

@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 

Para las opciones 1, la pregunta es cómo redactar la respuesta del delegado. La primera manera que consideré es (los nombres de las funciones son mínimas por simplicidad)

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

En mi experiencia, opción de devolución de llamada 1 usando un protocolo genérico era difícil de usar ya que si una clase quería ser una devolución de llamada para operaciones múltiples no pudo distinguir qué devolución de llamada estaba recibiendo.

La opción de devolución de llamada2 era engorrosa de usar y no se sentía natural de usar. Además, si el protocolo se extendió, sería necesario modificar cada llamada.

devolución de llamada Opción 1 usando un protocolo específicopara cada proceso parece ser el método más fácil de leer y escalable, sin embargo me pregunto si hacer un nuevo protocolo para cada función es demasiado prolijo (Di un objeto dado tiene 10+ tales 'operaciones largas', luego 10 protocolos diferentes).

¿Qué conclusiones han llegado a otras personas al implementar dichos diseños?

--edit: En respuesta a la respuesta de Dave DeLong

tengo tres clases que tienen '' operaciones largas, no de las operaciones en cada clase o entre clases están muy relacionados. Algunas son solicitudes de recursos de red, otras son solicitudes de procesamiento largas.

--edit: Una nota al margen, me parece que tienen un problema en el que no puedo invocar selectores bucle de ejecución para los mensajes que tienen más de un argumento. ¿Es esto una limitación de diseño o hay una forma de evitar esto?

Por ejemplo, tengo un mensaje tal como - (id) someMessage: (id) otherData value1: (id) VALUE2 MoreData: (id) Value3

Las funciones performSelector qué cola Runloop de no apoyan dichos selectores .

Respuesta

5

Elegí longRunningProcess2 en lugar de longRunningProcess3 simplemente porque es más fácil de entender si puede ver las declaraciones de métodos en el protocolo, en lugar de basarse en la documentación para averiguar cuáles son los argumentos del método de devolución de llamada.

Me gustaría agregar que Apple usa bloques para devoluciones de llamada en la API nueva de 10.6, que le da otra opción si no está soportando 10.5 o una versión anterior.

El enfoque de bloques se vería así:

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

Y usted llamaría el método de esta manera:

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

Acepto que nombrar explícitamente las devoluciones de llamada, aunque sea detallado, conduce a una mejor legibilidad. Todavía no he leído en 'bloques', pero se ven mucho más flexibles que los selectores. Siempre sentí que el sobrecargado de un parámetro/dos parámetros realiza funciones de selector donde se produce un truco y un resultado de mal diseño. – Akusete

3

me gustaría ir con una sola ruta de protocolo, similar a la opción CallbackProtocolGeneric, excepto que me gustaría ampliarlo a ser más como:

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

Así que es como la opción 1 en la que usted tiene una sola protocolo, pero al igual que la opción 2 en la que está devolviendo más información. Si es necesario, se podría ampliar aún más este con algo como:

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

El parámetro "paso" podría haber algún valor que indica cuál de los "10+ operaciones largas" que este objeto particular está haciendo.

Por supuesto, este consejo es muy genérico, ya que su pregunta también carece de información específica, pero esta es probablemente la dirección que seguiría (sin saber más).

+0

+1 Esto es similar a cómo las clases de cacao a menudo trabajan. Por ejemplo, los métodos de protocolo NSTableViewDelegate siempre proporcionan la vista de tabla que provocó la devolución de llamada como parámetro. –

+0

+1, gracias. En He agregado información más específica a mi pregunta. – Akusete

+0

Por alguna razón, puedo ver este método que me obliga a hacer una declaración de cambio en la implementación del delegado, lo que parece estar mal. Especialmente si las operaciones no están relacionadas. – Akusete

Cuestiones relacionadas