Cómo realizar devoluciones de llamada en Objective-C

119

¿Cómo realizar funciones de devolución de llamada en Objective-C?

Solo me gustaría ver algunos ejemplos completos y debería entenderlos.

ReachConnection
fuente

Respuestas:

94

Normalmente, las devoluciones de llamada en el objetivo C se realizan con delegados. A continuación, se muestra un ejemplo de una implementación de delegado personalizado;


Archivo de cabecera:

@interface MyClass : NSObject {
    id delegate;
}
- (void)setDelegate:(id)delegate;
- (void)doSomething;
@end

@interface NSObject(MyDelegateMethods)
- (void)myClassWillDoSomething:(MyClass *)myClass;
- (void)myClassDidDoSomething:(MyClass *)myClass;
@end

Archivo de implementación (.m)

@implementation MyClass
- (void)setDelegate:(id)aDelegate {
    delegate = aDelegate; /// Not retained
}

- (void)doSomething {
    [delegate myClassWillDoSomething:self];
    /* DO SOMETHING */
    [delegate myClassDidDoSomething:self];
}
@end

Eso ilustra el enfoque general. Crea una categoría en NSObject que declara los nombres de sus métodos de devolución de llamada. NSObject en realidad no implementa estos métodos. Este tipo de categoría se llama protocolo informal, solo está diciendo que muchos objetos podrían implementar estos métodos. Son una forma de declarar hacia adelante la firma de tipo del selector.

A continuación, tiene algún objeto que sea el delegado de "MyClass" y MyClass llama a los métodos de delegado en el delegado según corresponda. Si las devoluciones de llamada de los delegados son opcionales, normalmente las protegerá en el sitio de envío con algo como "if ([delegate respondsToSelector: @selector (myClassWillDoSomething :)) {". En mi ejemplo, se requiere que el delegado implemente ambos métodos.

En lugar de un protocolo informal, también puede utilizar un protocolo formal definido con @protocol. Si lo hace, cambiaría el tipo de definidor delegado y la variable de instancia para que sea " id <MyClassDelegate>" en lugar de solo " id".

Además, notará que el delegado no se retiene. Normalmente, esto se hace porque el objeto que "posee" instancias de "MyClass" suele ser también el delegado. Si MyClass retuvo a su delegado, entonces habría un ciclo de retención. Es una buena idea en el método dealloc de una clase que tiene una instancia de MyClass y es su delegado borrar esa referencia de delegado, ya que es un puntero hacia atrás débil. De lo contrario, si algo mantiene viva la instancia de MyClass, tendrá un puntero colgando.

Jon Hess
fuente
+1 Buena respuesta completa. La guinda del pastel sería un enlace a documentación más detallada de Apple sobre los delegados. :-)
Quinn Taylor
Jon, muchas gracias por tu ayuda. Realmente aprecio tu ayuda. Lamento esto, pero no tengo muy clara la respuesta. Message .m es una clase que se establece a sí misma como delegada durante la llamada a la función doSomething. ¿Es doSomething la función de devolución de llamada a la que llama el usuario? ya que tengo la impresión de que el usuario llama a doSomething, y sus funciones de devolución de llamada son myClassWillDoSomethingg y myClassDidDoSomething. Además, ¿puede mostrarme cómo crear una clase superior que llame a la función de devolución de llamada? Soy un programador de C, por lo que aún no estoy muy familiarizado con el entorno de Obj-C.
ReachConnection
"Mensaje .m" solo quería decir, en su archivo .m. Tendrías una clase separada, llamémosla "Foo". Foo tendría una variable "MyClass * myClass", y en algún momento Foo diría "[myClass setDelegate: self]". En cualquier momento después de eso, si alguien incluido foo invocaba el método doSomethingMethod en esa instancia de MyClass, foo tendría sus métodos myClassWillDoSomething y myClassDidDoSomething invocados. De hecho, también publicaré un segundo ejemplo diferente que no usa delegados.
Jon Hess
No creo que .m sea sinónimo de "mensaje".
Chuck
140

Para completar, dado que StackOverflow RSS simplemente resucitó aleatoriamente la pregunta para mí, la otra opción (más nueva) es usar bloques:

@interface MyClass: NSObject
{
    void (^_completionHandler)(int someParameter);
}

- (void) doSomethingWithCompletionHandler:(void(^)(int))handler;
@end


@implementation MyClass

- (void) doSomethingWithCompletionHandler:(void(^)(int))handler
{
    // NOTE: copying is very important if you'll call the callback asynchronously,
    // even with garbage collection!
    _completionHandler = [handler copy];

    // Do stuff, possibly asynchronously...
    int result = 5 + 3;

    // Call completion handler.
    _completionHandler(result);

    // Clean up.
    [_completionHandler release];
    _completionHandler = nil;
}

@end

...

MyClass *foo = [[MyClass alloc] init];
int x = 2;
[foo doSomethingWithCompletionHandler:^(int result){
    // Prints 10
    NSLog(@"%i", x + result);
}];
Jens Ayton
fuente
2
@Ahruman: ¿Qué significa el carácter "^" en "void (^ _completionHandler) (int someParameter);" ¿media? ¿Podrías explicar qué hace esa línea?
Konrad Höffner
2
¿Puede proporcionar una explicación de por qué necesita copiar el controlador de devolución de llamada?
Elliot Chance
52

Aquí hay un ejemplo que mantiene los conceptos de delegados fuera, y solo hace una llamada cruda.

@interface Foo : NSObject {
}
- (void)doSomethingAndNotifyObject:(id)object withSelector:(SEL)selector;
@end

@interface Bar : NSObject {
}
@end

@implementation Foo
- (void)doSomethingAndNotifyObject:(id)object withSelector:(SEL)selector {
    /* do lots of stuff */
    [object performSelector:selector withObject:self];
}
@end

@implementation Bar
- (void)aMethod {
    Foo *foo = [[[Foo alloc] init] autorelease];
    [foo doSomethingAndNotifyObject:self withSelector:@selector(fooIsDone:)];
}

- (void)fooIsDone:(id)sender {
    NSLog(@"Foo Is Done!");
}
@end

Normalmente, el método - [Foo doSomethingAndNotifyObject: withSelector:] sería asíncrono, lo que haría que la devolución de llamada fuera más útil que aquí.

Jon Hess
fuente
1
Muchas gracias John. Entiendo su primera implementación de devolución de llamada después de sus comentarios. Además, su segunda implementación de devolución de llamada es más sencilla. Ambos son muy buenos.
ReachConnection
1
Gracias por publicar este Jon, fue muy útil. Tuve que cambiar [object performSelectorwithObject: self]; a [objeto performSelector: selector withObject: self]; para que funcione correctamente.
Banjer
16

Para mantener esta pregunta actualizada, la introducción de ARC en iOS 5.0 significa que esto se puede lograr usando Blocks de manera aún más concisa:

@interface Robot: NSObject
+ (void)sayHi:(void(^)(NSString *))callback;
@end

@implementation Robot
+ (void)sayHi:(void(^)(NSString *))callback {
    // Return a message to the callback
    callback(@"Hello to you too!");
}
@end

[Robot sayHi:^(NSString *reply){
  NSLog(@"%@", reply);
}];

Siempre hay una maldita sintaxis de bloques si alguna vez olvidas la sintaxis de bloques de Objective-C.

Ryan Brodie
fuente
En @interface debe ser + (void)sayHi:(void(^)(NSString *reply))callback;no+ (void)sayHi:(void(^)(NSString *))callback;
Tim007
No de acuerdo con la sintaxis de F **** ng Block antes mencionada : - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;(Nota parameterTypesno parameters)
Ryan Brodie
4

Devolución de llamada: hay 4 tipos de devolución de llamada en Objective C

  1. Tipo de selector : puede ver NSTimer, UIPangesture son los ejemplos de devolución de llamada del selector. Se utiliza para una ejecución de código muy limitada.

  2. Tipo de delegado : común y más utilizado en el marco de Apple. UITableViewDelegate, NSNURLConnectionDelegate. Por lo general, se utilizan para mostrar la descarga de muchas imágenes del servidor de forma asincrónica, etc.

  3. NSNotifications : NotificationCenter es una de las características de Objective C que solía notificar a muchos destinatarios en el momento en que ocurre el evento.
  4. Bloques : los bloques se usan más comúnmente en la programación de Objective C. Es una gran característica y se utiliza para ejecutar fragmentos de código. También puede consultar el tutorial para comprender: Tutorial de bloques

Por favor, déjeme si tiene alguna otra respuesta. Te lo agradeceré.

Anil Gupta
fuente