Objective-C: notificaciones y delegados

En Objective-C, tenemos dos maneras utilizadas para la recepción y el envío de mensajes entre las clases: las notificaciones y los delegados. La diferencia entre los dos, así como en el nivel de aplicación, depende sustancialmente de "cuánto" - objetos - puede recibir un mensaje. Primero deja que te enseñe cómo lo hace el concepto de delegado.

Delegar

Imaginemos que tenemos nuestra clase habitual A (nuestro receptor, en este ejemplo), que crea una instancia de un objeto de la clase B (el transmisor). La clase A quiere ser notificado cuando una determinada operación, llevada a cabo por la clase B, se ha completado. Una implementación de crudo de este concepto, totalmente legítima y en funcionamiento, sería: Clase A pasa un puntero a la clase B, de modo que cuando éste quiere "advertir" a la Clase A no deberá dejar de invocar un método mediante el puntero a su pasado.

El código de la clase B, entonces, sería:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/ / ------------------------------------------------ ----------------------
/ / Archivo ClassB.h

@ Clase ClassA;
NSObject { @ Interface ClassB: NSObject {
ClassA * pointerClassA;
}
/ / Yo proporciono una propiedad con la que la clase A puede
/ / Pasar el puntero
assign ) ClassA * pointerClassA; @ Property (asignar) ClassA * pointerClassA;

@ End

/ / ------------------------------------------------ ----------------------
/ / ClassB.m archivo

# Importar "ClassB.h"
# Importar "ClassA.h"

@ Implementación ClassB

@ Sintetizar pointerClassA;

void ) operazione { - (Void) {operación
/ / ... operación
/ / Enviar la Clase Un aviso de término
; [PointerClassA metodoDiAvviso];
}

@ End

Clase A Clase B sabe que acepta un puntero a su (Clase A y Clase B se conocen entre sí, pero ... lo que necesita saber en este ejemplo) también sabe que cuando el B-Class habrá completado su tarea invocará un método llamado metodoDiAvviso . Por lo tanto, la clase A, que tiene que ver que para aplicar este método y pasar una Clase B de su puntero. Todo esto se tradujo en código, es:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/ / ------------------------------------------------ ----------------------
/ / Archivo ClassA.h

@ Clase ClassB;
<NSObject> { @ Interface ClassA: {<NSObject>
ClassB * ClassB;
}

void ) metodoDiAvviso; - (Void) metodoDiAvviso;
void ) metodoQualsiasi; - (Void) metodoQualsiasi;

@ End

/ / ------------------------------------------------ ----------------------
/ / ClassA.m archivo

# Importar "ClassA.h"
# Importar "ClassB.h"

@ Implementación ClassA

id ) init { - (Id) init {
super init ] ; self = [super init];
self ) { if (auto) {
ClassB alloc ] init ] ; ClassB = [[ClassB alloc] init];
classB.pointerA = yo / / I comunicar mi puntero a la Clase B
}
volver yo;
}

void ) metodoQualsiasi { - (Void) {metodoQualsiasi
/ / ...
; [ClassB operación];
}

void ) metodoDiAvviso { - (Void) {metodoDiAvviso
/ / Método invocado por la Clase B
"%s: la Classe B ha terminato e mi ha avvisato!" , __FUNCTION__ ) ; NSLog (@ "% s: el B-Class ha terminado y me advirtió", __ FUNCTION__);
}

@ End

Aunque esta técnica funciona, muestra todas sus limitaciones cuando se usan en contextos también pequeñas. Vamos a ver cuáles son las cosas que no son:

  • Las clases deben conocerse entre sí: cada uno ha importado las definiciones de la otra
  • El A-Class debe aplicar y declarar el receptor método
  • La Clase B se supone que hay un Clase A, es decir, el puntero pointerA ser explotado
  • Todo está hecho explícitos los nombres de las clases en el juego
  • Si desea enviar un mensaje de Clase B a la Clase C debe ser: o cambiar el código o duplicar el tour

Afortunadamente, hay una manera mucho más elegante, clara y fácil de usar para hacer frente a estas situaciones. Ya hemos hablado acerca de cómo crear un delegado con su propio protocolo . Objective-C permite definir un protocolo de comunicación a través del cual una o más clases pueden intercambiar información.

Nota: Es posible que los protocolos de Objective-C se indican como una manera de heredar la interfaz (no la aplicación) de otras clases. Esto es cierto, aunque hay algunas diferencias sustanciales en este caso en comparación con lo que ocurre con otros idiomas.

En nuestro caso, el protocolo debe estar definido dentro de la Clase B, Clase A y aprobada por la En la práctica, una clase define lo que es el protocolo de comunicación. Las clases que tengo la intención de recibir los mensajes de este último, se adoptan los protocolos.
Repasemos el ejemplo anterior y vuelva a grabar utilizando protocolos y delegados:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/ / ------------------------------------------------ ----------------------
/ / Archivo ClassB.h

@ ClassBDelegate Protocolo;
NSObject { @ Interface ClassB: NSObject {
Identificación <ClassBDelegate> delegada;
}
/ / Delegado
assign ) id<ClassBDelegate> delegate; @ Property (asignar) Identificación <ClassBDelegate> delegada;

@ End

@ Protocolo ClassBDelegate <NSObject>
@ Opcional
/ / Mensaje enviado al delegado cuando la operación
void ) metodoDiAvviso; - (Void) metodoDiAvviso;
@ End

/ / ------------------------------------------------ ----------------------
/ / ClassB.m archivo

# Importar "ClassB.h"

@ Implementación ClassB

@ Sintetizar delegado;

void ) operazione { - (Void) {operación
/ / ... operación
/ / Si hay un delegado y delegado que cumpla con el protocolo
/ / ClassBDelegate enviar notificaciones de acabado
delegate && [ delegate respondsToSelector : @selector ( metodoDiAvviso ) ] ) { if (&& delegado [delegado respondsToSelector: @ selector (metodoDiAvviso)]) {
; [Delegada metodoDiAvviso];
}
}
@ End

En el nuevo Código, que ya se nota un avance significativo: no hay ninguna indicación en la Clase A. Este último, por su parte, en vez debería adoptar el protocolo explícitamente puesto a disposición por la Clase B, si quiere ser un delegado y recibir notificaciones:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/ / ------------------------------------------------ ----------------------
/ / Archivo ClassA.h

# Importar "ClassB.h" / / adopto el ClassBDelegate protocolo

< NSObject , ClassBDelegate> { @ Interface ClassA: < NSObject , ClassBDelegate> {
ClassB * ClassB;
}

void ) metodoQualsiasi; - (Void) metodoQualsiasi;

@ End

/ / ------------------------------------------------ ----------------------
/ / ClassA.m archivo

# Importar "ClassA.h"

@ Implementación ClassA

id ) init { - (Id) init {
super init ] ; self = [super init];
self ) { if (auto) {
ClassB alloc ] init ] ; ClassB = [[ClassB alloc] init];
/ / Comunicarse con la Clase B sea el que su suplente
classB.delegate = sí;
}
volver yo;
}

void ) metodoQualsiasi { - (Void) {metodoQualsiasi
/ / ...
; [ClassB operación];
}

/ / De acuerdo con el protocolo aprobado
void ) metodoDiAvviso { - (Void) {metodoDiAvviso
/ / Método invocado por la Clase B
"%s: la Classe B ha terminato e mi ha avvisato!" , __FUNCTION__ ) ; NSLog (@ "% s: el B-Class ha terminado y me advirtió", __ FUNCTION__);
}

@ End

Este proceso es mucho más limpio y más sencillo de implementar. Las ventajas son:

  • La clase que desea delegar (Clase B) no debe conocer a sus delegados, lo suficiente para él y cuando cumplan con su protocolo. De esta manera, cualquier clase puede recibir mensajes
  • La clase que desea recibir el mensaje (Clase A) adopta explícitamente el / i protocolo / s, que se puede tomar en parte o en su totalidad en función de cómo haya definido

El único problema con este enfoque se relaciona con el número de delegados. En nuestro ejemplo, el caso de la Clase B fue creado para toda la clase A. En este contexto, es obvio (pero no necesariamente) que es la Clase A a ser el delegado. En una variante diferente puede crear una instancia de clase A Clase C y suministrarla como delegado a la Clase B (Clase C tendrá que adoptar el ClassBDelegate Protocol). Sin embargo, lo que está claro es que una instancia está ligada solo delegado, Clase B no puede tener dos o más delegados, no proporcionan ninguna propiedad por valor de delegate .

Denominación

Disfrute de sus palabras sobre la denominación, es decir, sobre el nombramiento de los protocolos y métodos de esta última. El nombre del protocolo, si quiere seguir en el Apple reglas, se utiliza normalmente para pasar el Delegate al final del nombre de la clase que define el protocolo. En otros casos, cuando el protocolo no está ligado a ninguna clase en particular, utiliza el truco ...ing Inglés, que no debe confundirse con la clase de protocolo. Así, por ejemplo:

1
2
3
4
NSLook se NSLooking, NSLook parece ser una clase
ClassB definir su ClasseBDelegate protocolo
UIWebView define UIWebViewDelegate
...

El método definido en el protocolo, en cambio, sigue la regla que siempre se debe pasar el puntero de quien envió el mensaje, seguido de los parámetros de su caso. Por ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BOOL ) tableView : ( NSTableView * ) tableView shouldSelectRow : ( int ) row; - (BOOL) tableView: ( NSTableView *) tableView shouldSelectRow: (int) fila;
BOOL ) application : ( NSApplication * ) sender openFile : ( NSString * ) filename; - (BOOL) aplicación: ( NSApplication *) emisor abrirArchivo: ( NSString *) nombre del archivo;

/ / Si no hay parámetros
BOOL ) applicationOpenUntitledFile : ( NSApplication * ) sender; - (BOOL) applicationOpenUntitledFile: ( NSApplication *) remitente;

/ / Utilizamos "Did" o "voluntad" para notificar al delegado que algo es
/ / El éxito o de que algo va a suceder
void ) browserDidScroll : ( NSBrowser * ) sender; - (Void) browserDidScroll: ( NSBrowser *) remitente;
NSUndoManager * ) windowWillReturnUndoManager : ( NSWindow * ) window; - ( NSUndoManager *) windowWillReturnUndoManager: ( NSWindow *) ventana;

/ / El prefijo "debería" se utiliza en lugar de pedir al delegado
/ / Si quieres que algo suceda
BOOL ) windowShouldClose : ( id ) sender; - (BOOL) windowShouldClose: (id) del remitente;

Notificaciones

Cuando un objeto o una instancia de una clase pueden ser compartidos por varios objetos y usted tiene la necesidad de alertar a todos los objetos afectados, se debe utilizar un método diferente para notificar mensajes, las llamadas notificaciones.
Notificaciones y protocolos / delegados pueden coexistir fácilmente en el mismo código, lo que significa que si ya se ha implementado un protocolo / un delegado, no hay nada que añadir notificaciones. entonces deja que te enseñe un ejemplo usando sólo sus notificaciones iniciales.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/ / ------------------------------------------------ ----------------------
/ / Archivo ClassB.h

NSObject { @ Interface ClassB: NSObject {
}

@ End

/ / ------------------------------------------------ ----------------------
/ / ClassB.m archivo

# Importar "ClassB.h"

@ Implementación ClassB

@ Sintetizar pointerClassA;

void ) operazione { - (Void) {operación
/ / ... operación
/ / Enviar una notificación a todos los que se han registrado
defaultCenter ] postNotificationName : @ "operazioneTerminata" ] ; [[ NSNotificationCenter defaultCenter] postNotificationName: @ "operazioneTerminata"];
}

@ End

Como puede verse en el código anterior, esta técnica no añade nada, que no sea el envío de la notificación. Los que quieren ser notificado tendrá que hacer muy poco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/ / ------------------------------------------------ ----------------------
/ / Archivo ClassA.h

<NSObject> { @ Interface ClassA: {<NSObject>
ClassB * ClassB;
}

void ) metodoQualsiasi; - (Void) metodoQualsiasi;

@ End

/ / ------------------------------------------------ ----------------------
/ / ClassA.m archivo

# Importar "ClassA.h"

@ Implementación ClassA

id ) init { - (Id) init {
super init ] ; self = [super init];
self ) { if (auto) {
ClassB alloc ] init ] ; ClassB = [[ClassB alloc] init];
/ / Me registro como interesados ​​en el mensaje "operazioneTerminata"
defaultCenter ] addObserver : self [[ NSNotificationCenter defaultCenter] addObserver: self
( notificaOperazioneTerminata ) selector: @ selector (notificaOperazioneTerminata)
"operazioneTerminata" Nombre: @ "operazioneTerminata"
] ; objeto: nil];
}
volver yo;
}

void ) metodoQualsiasi { - (Void) {metodoQualsiasi
/ / ...
; [ClassB operación];
}

/ / El nombre de este método decide la Clase A y puede ser
/ / Entonces, cualquier
void ) notificaOperazioneTerminata { - (Void) {notificaOperazioneTerminata
/ / Método invocado por la Clase B
"%s: la Classe B ha terminato e mi ha avvisato!" , __FUNCTION__ ) ; NSLog (@ "% s: el B-Class ha terminado y me advirtió", __ FUNCTION__);
}

void ) dealloc { - (Void) dealloc {
/ / Quito a mí mismo de la recepción de las notificaciones relativas a la
/ / Mensaje identificado por la cadena @ "operazioneTerminata"
defaultCenter ] removeObserver : self [[ NSNotificationCenter defaultCenter] removeObserver: self
"operazioneTerminata" Nombre: @ "operazioneTerminata"
] ; objeto: nil];
; [Súper dealloc];
}

@ End

Denominación

Aquí, también, unas palabras sobre el nombramiento de las notificaciones, siempre de acuerdo con la sugerencia de Apple. El nombre de la notificación, que es un NSString , se construye normalmente de acuerdo con este criterio:

1
+ [ Did | Will ] + [ UniquePartOfName ] + Notification [Nombre de la clase asociada] + [DID | Will] + [UniquePartOfName] + Notificación

En el ejemplo anterior, tendríamos que poner en el archivo H Clase B.:

1
2
3
4
5
6
7
8
9
10
/ / En el archivo de encabezado de la clase B
* const ClassBDidFinishedNotification; extern NSString * const ClassBDidFinishedNotification;

/ / En el archivo de implementación de la Clase B
const ClassBDidFinishedNotification = @ "ClassBDidFinishedNotification" ; NSString * const ClassBDidFinishedNotification = @ "ClassBDidFinishedNotification";

/ / El método de la clase A, sin embargo, es más libre, pero puede ser escrito
/ / Por ejemplo añadiendo "Notificación" al final - esto, sin embargo, tiene menos
/ / Importancia
void ) finishedNotification; - (Void) finishedNotification;

Parámetros

Ambas técnicas descritas anteriormente permiten enviar mensajes con número arbitrario de parámetros. Tomamos el ejemplo anterior y tratar de pasar dos parámetros. En el caso del protocolo / delegado, podríamos tener:

1
2
3
4
5
@ Protocolo ClassBDelegate <NSObject>
@ Opcional
/ / Mensaje enviado al delegado cuando la operación
void ) classB : ( ClassB * ) aClassB avvisoConID : ( int ) aID; - (Void) ClassB: (ClassB *) aClassB avvisoConID: (int) de la ayuda;
@ End

La aplicación debería haberse escrito:

1
2
3
4
5
6
7
8
void ) operazione { - (Void) {operación
/ / ... operación
/ / Si hay un delegado y delegado que cumpla con el protocolo
/ / ClassBDelegate enviar notificaciones de acabado
delegate && [ delegate respondsToSelector : @selector ( classB : avvisoConID : ) ] ) { if (&& delegado [delegado respondsToSelector: @ selector (ClassB: avvisoConID :)]) {
self avvisoConID : 57 ] ; [ClassB delegado: self avvisoConID: 57];
}
}

Como se puede ver muy, muy simple.

) contiene un puntatore ad un NSDictionary con la collezione dei parametri. Para las notificaciones, el discurso es similar, aunque en este caso se utiliza un solo parámetro userInfo que normalmente (si no nil ) contiene un puntero a un NSDictionary con la colección de parámetros. En el momento en que la notificación habría tenido:

1
2
3
4
5
6
7
8
void ) operazione { - (Void) {operación
/ / ... operación
/ / Enviar una notificación a todos los que se han registrado
defaultCenter ] postNotificationName : @ "operazioneTerminata" [[ NSNotificationCenter defaultCenter] postNotificationName: @ "operazioneTerminata"
objeto: self
NSDictionary dictionaryWithObject : [ NSNumber numberWithInt : 57 ] userInfo: [ NSDictionary dictionaryWithObject: [ NSNumber numberWithInt: 57]
"avvisoConID" ] ] ; forKey: @ "avvisoConID"]];
}

En el momento de la lectura de la notificación:

1
2
3
4
5
6
7
void ) notificaOperazioneTerminata : ( NSNotification * ) aNotification { - (Void) notificaOperazioneTerminata: ( NSNotification *) {aNotification
/ / Método invocado por la Clase B
"%s: la Classe B ha terminato e mi ha avvisato!" , __FUNCTION__ ) ; NSLog (@ "% s: el B-Class ha terminado y me advirtió", __ FUNCTION__);

avvisoConID = [ [ aNotification userInfo ] objectForKey : @ "avvisoConID" ] ; NSNumber * avvisoConID = [[aNotification userInfo] objectForKey: @ "avvisoConID"];
"%s: %d" , __FUNCTION__, [ avvisoConID intValue ] ) ; NSLog (@ "% s:% d", __ FUNCTION__, [avvisoConID intValue]);
}

En conclusión, al utilizar delegados / protocolos y las notificaciones?

Como hemos visto las dos soluciones son simples, de gran alcance y fácil de implementar. Si los eventos de la instancia de nuestra clase puede afectar a múltiples objetos, no tenemos más remedio que utilizar las notificaciones. En todos los demás casos, los delegados / protocolos o ambas soluciones.
Normalmente recomendamos a partir de la aplicación de los protocolos y de los delegados, como si sucediera que un evento es de interés colectivo, siempre se puede enviar en la notificación.

5 comentarios a: " "

  1. 11 de marzo 2011 Paul :

    Excelente artículo!
    Claro, preciso y lleno de ejemplos!
    Blog muy interesante!
    Saludos

  2. 20 de noviembre 2011 ilew:

    No es que usted podría tener un último ejemplo de la utilización de los delegados? Aquí nos limitamos a la definición, pero un ejemplo práctico de cómo usarlo (en referencia al código del ejemplo)?

  3. 24 de noviembre 2011 Objective-C: adición de las notificaciones y de los delegados - Undolog.com - Undolog.com :

    [...] La cuestión de ilew con un artículo genuino para explicar mejor, ejemplo adjuntando, cómo funcionan y los delegados [...]

  4. 02 de febrero 2012 louis:

    muy clara y sencilla
    Debo admitir que la escritura de un pa apenas utilice delegados creados por mí ... por supuesto que es una técnica que me he comprometido a utilizar con más frecuencia, aunque forzando la necesidad
    felicitaciones

Deja un comentario

TAG XHTML PERMISOS: ENTRADA CÓDIGO:
 <pre></pre> // blocco generico <code></code> // blocco generico [cc_actionscript][/cc_actionscript] // Actionscript [cc_actionscript3][/cc_actionscript3] // Actionscript 3 [cc_css][/cc_css] // CSS Style Sheet [cc_html][/cc_html] // HTML [cc_js][/cc_js] // Javascript [cc_objc][/cc_objc] // Objective-C [cc_php][/cc_objc] // PHP [cc_sql][/cc_sql] // SQL