Objective-C: notificações e delegados

Em Objective-C, temos duas maneiras utilizadas para receber e enviar mensagens entre classes: as notificações e delegados. A diferença entre os dois, assim como ao nível da aplicação, depende substancialmente "quanto" - objectos - pode receber uma mensagem. Primeiro, deixe-me mostrar-lhe como é que o conceito de delegado.

Delegar

Imagine que nós temos a nossa classe habitual A (nosso receptor, neste exemplo) que instancia um objeto da classe B (o nosso transmissor). A classe A quer ser notificado quando uma determinada operação, realizada pela classe B, está concluída. Uma implementação em bruto deste conceito, completamente legítimo e funcionando, seria: Classe A passa um ponteiro para a sua classe B, de modo que, quando este quer "avisar" da Classe A, não deve deixar de invocar um método usando o ponteiro para o seu passado.

O código da classe B e, em seguida, seria:

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
/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassB.h

@ Classe ClassA;
NSObject { @ Interface ClassB: NSObject {
ClassA * pointerClassA;
}
/ / Eu fornecer uma propriedade com que o Classe A pode
/ / Passa o ponteiro
assign ) ClassA * pointerClassA; @ Property (atribuir) ClassA * pointerClassA;

@ End

/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassB.m

# Import "ClassB.h"
# Import "ClassA.h"

@ Implementação ClassB

@ Sintetizar pointerClassA;

void ) operazione { - (Void) {operação
/ / ... operação
/ / Envia o Classe A notificação de acabamento
; [PointerClassA metodoDiAvviso];
}

@ End

A Classe Classe B sabe que aceita um ponteiro para seu (Classe A e Classe B se conhecem, mas ... você precisa saber neste exemplo) também sabe que quando o B-Class terá completado sua tarefa irá chamar um método chamado metodoDiAvviso . Então, Classe A, tem que fazer isso para implementar tal método e passar um Classe B seu ponteiro. Tudo isso traduzido em código, é:

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
/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassA.h

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

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

@ End

/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassA.m

# Import "ClassA.h"
# Import "ClassB.h"

@ Implementação ClassA

id ) init { - (Id) de inicialização {
super init ] ; auto = [super init];
self ) { if (self) {
ClassB alloc ] init ] ; classB = [[ClassB alloc] init];
classB.pointerA = self / / Eu me comunico minha ponteiro para o Classe B
}
retornar auto;
}

void ) metodoQualsiasi { - (Void) {metodoQualsiasi
/ / ...
; [ClassB operação];
}

void ) metodoDiAvviso { - (Void) {metodoDiAvviso
/ / Método chamado pela Classe B
"%s: la Classe B ha terminato e mi ha avvisato!" , __FUNCTION__ ) ; NSLog (@ "% s: o B-Class acabou e me avisou", __ FUNCTION__);
}

@ End

Embora esta técnica funções, mostra todas as suas limitações quando utilizado em contextos também pequenas. Vamos ver quais são as coisas que não são:

  • Classes devem conhecer uns aos outros: cada um tem importado as outras definições
  • O A-Class deve implementar e declarar o receptor método
  • A classe B assume que existe um Class, isto é, o ponteiro pointerA ser explorada
  • Tudo é feito explicitar os nomes das classes do jogo
  • Se você quiser enviar uma mensagem de Classe B para a Classe C deve ser: o alterar o código ou duplicar a turnê

Felizmente, há um muito mais elegante, clara e simples de usar para lidar com essas situações. Nós já falamos sobre em Como criar um delegado com seu próprio protocolo . Objective-C permite definir um protocolo de comunicação através do qual uma ou mais classes podem trocar informações.

Nota: Você pode achar que os protocolos de Objective-C são indicados como uma forma de herdar a interface (e não a execução) de outras classes. Isto é verdade, embora haja algumas diferenças substanciais neste caso, em comparação com o que acontece com outras linguagens.

No nosso caso, o protocolo deve ser definido na classe B, e adotado pela classe A. Na prática, uma classe define qual é o seu protocolo de comunicação. As classes que pretendo receber mensagens do último, adotamos os protocolos.
Vamos analisar o exemplo anterior e depois reescrevê-lo por meio de protocolos e 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
/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassB.h

@ Protocol ClassBDelegate;
NSObject { @ Interface ClassB: NSObject {
id <ClassBDelegate> delegada;
}
/ / Delegado
assign ) id<ClassBDelegate> delegate; @ Property (atribuir) ID <ClassBDelegate> delegada;

@ End

@ Protocol ClassBDelegate <NSObject>
@ Opcional
/ / Mensagem enviada ao delegado quando a operação
void ) metodoDiAvviso; - (Void) metodoDiAvviso;
@ End

/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassB.m

# Import "ClassB.h"

@ Implementação ClassB

@ Sintetizar delegado;

void ) operazione { - (Void) {operação
/ / ... operação
/ / Se houver um delegado e delegado que está em conformidade com o protocolo
/ / ClassBDelegate enviar avisos de acabamento
delegate && [ delegate respondsToSelector : @selector ( metodoDiAvviso ) ] ) { if (&& delegado [delegado respondsToSelector: @ selector (metodoDiAvviso)]) {
; [Delegado metodoDiAvviso];
}
}
@ End

No novo código, já podemos notar um significativo passo em frente: não há nenhuma indicação sobre a Classe A. Este último, por sua vez, deve adotar em vez do protocolo explicitamente disponibilizados pela Classe B se ele quer ser um delegado e, em seguida, receber notificaçõ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
42
/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassA.h

# Import "ClassB.h" / / eu adotar o protocolo ClassBDelegate

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

void ) metodoQualsiasi; - (Void) metodoQualsiasi;

@ End

/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassA.m

# Import "ClassA.h"

@ Implementação ClassA

id ) init { - (Id) de inicialização {
super init ] ; auto = [super init];
self ) { if (self) {
ClassB alloc ] init ] ; classB = [[ClassB alloc] init];
/ / Comunique-se com a classe B para ser o seu vice
classB.delegate = auto;
}
retornar auto;
}

void ) metodoQualsiasi { - (Void) {metodoQualsiasi
/ / ...
; [ClassB operação];
}

/ / De acordo com o protocolo adotado
void ) metodoDiAvviso { - (Void) {metodoDiAvviso
/ / Método chamado pela Classe B
"%s: la Classe B ha terminato e mi ha avvisato!" , __FUNCTION__ ) ; NSLog (@ "% s: o B-Class acabou e me avisou", __ FUNCTION__);
}

@ End

Este processo é muito mais limpo e mais simples de implementar. As vantagens são:

  • A classe que quer delegar (Classe B) não deve saber seus delegados, o suficiente para ele que eles cumpram com o seu protocolo. Desta forma, qualquer classe pode receber mensagens
  • A classe que queira receber a mensagem (Classe A) adota explicitamente a / i protocolo / s, que pode ser tomado em parte ou em sua totalidade com base em como você definiu

O único problema com esta abordagem é relacionada com o número de pessoas. No nosso exemplo, a instância da classe B foi criado para toda a classe A. Neste contexto, é óbvio (mas não necessariamente), que é de classe A para ser o delegado. Em uma variante diferente poderia instanciar Classe A Classe C e fornecê-lo como um delegado de Classe B (Classe C vai ter que adotar o ClassBDelegate Protocol). No entanto, o que está claro é que uma instância está ligada apenas um delegado, Classe B não pode ter dois ou mais delegados, não fornecem bens no valor de delegate .

Nomeando

Passar as suas palavras na nomenclatura, isto é, sobre a nomenclatura de protocolos e métodos deste último. O nome do protocolo, se você quiser seguir as regras da Apple, que você normalmente usa para pendurar Delegate no final do nome da classe que define o protocolo. Em outros casos, quando o protocolo não está ligado a qualquer classe particular, a utilização da forma de asa ...ing Inglês, não deve ser confundida com a classe de protocolos. Assim, por exemplo:

1
2
3
4
Nslook vai NSLooking, nslook parece ser uma classe
ClassB definir sua ClasseBDelegate protocolo
UIWebView define UIWebViewDelegate
...

O método definido no protocolo, ao contrário, segue a regra que deve sempre passar o ponteiro de quem enviou a mensagem, seguido pelos parâmetros, se houver. Por exemplo:

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) linha;
BOOL ) application : ( NSApplication * ) sender openFile : ( NSString * ) filename; - (BOOL) Aplicação: ( NSApplication *) remetente openFile: ( NSString *) nome;

/ / Se não há parâmetros
BOOL ) applicationOpenUntitledFile : ( NSApplication * ) sender; - (BOOL) applicationOpenUntitledFile: ( NSApplication *) remetente;

/ / Usamos "Did" ou "irão" para avisar ao delegado que algo é
/ / Sucesso ou que algo vai acontecer
void ) browserDidScroll : ( NSBrowser * ) sender; - (Void) browserDidScroll: ( NSBrowser *) remetente;
NSUndoManager * ) windowWillReturnUndoManager : ( NSWindow * ) window; - ( NSUndoManager *) windowWillReturnUndoManager: ( NSWindow *) janela;

/ / O prefixo "deve" é usado para pedir ao delegado vez
/ / Se você quer que algo aconteça
BOOL ) windowShouldClose : ( id ) sender; - (BOOL) windowShouldClose: (id) sender;

Notificações

Quando um objeto ou uma instância de uma classe pode ser compartilhado por vários objetos e você tem a necessidade de alertar todos os objetos afetados, você deve usar um método diferente para comunicar mensagens, os chamados notificações.
Notificações e protocolos / delegados podem facilmente coexistir dentro do mesmo código, o que significa que, se já implementou um protocolo / delegado, não há nada a acrescentar notificações. então deixe-me mostrar-lhe um exemplo usando apenas as suas notificações iniciais.

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

NSObject { @ Interface ClassB: NSObject {
}

@ End

/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassB.m

# Import "ClassB.h"

@ Implementação ClassB

@ Sintetizar pointerClassA;

void ) operazione { - (Void) {operação
/ / ... operação
/ / Envia uma notificação a todos aqueles que se inscreveram
defaultCenter ] postNotificationName : @ "operazioneTerminata" ] ; [[ NSNotificationCenter defaultCenter] postNotificationName: @ "operazioneTerminata"];
}

@ End

Como pode ser visto a partir do código de cima, esta técnica não acrescentam nada, excepto o envio da notificação. Aqueles que querem ser notificado terá que fazer muito pouco:

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
/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassA.h

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

void ) metodoQualsiasi; - (Void) metodoQualsiasi;

@ End

/ / ------------------------------------------------ ----------------------
/ / Arquivo ClassA.m

# Import "ClassA.h"

@ Implementação ClassA

id ) init { - (Id) de inicialização {
super init ] ; auto = [super init];
self ) { if (self) {
ClassB alloc ] init ] ; classB = [[ClassB alloc] init];
/ / Eu registar-se como interessados ​​na mensagem "operazioneTerminata"
defaultCenter ] addObserver : self [[ NSNotificationCenter defaultCenter] addObserver: self
( notificaOperazioneTerminata ) selector: @ selector (notificaOperazioneTerminata)
"operazioneTerminata" Nome: @ "operazioneTerminata"
] ; objeto: nil];
}
retornar auto;
}

void ) metodoQualsiasi { - (Void) {metodoQualsiasi
/ / ...
; [ClassB operação];
}

/ / O nome deste método decide o Classe A e pode ser
/ / Então qualquer
void ) notificaOperazioneTerminata { - (Void) {notificaOperazioneTerminata
/ / Método chamado pela Classe B
"%s: la Classe B ha terminato e mi ha avvisato!" , __FUNCTION__ ) ; NSLog (@ "% s: o B-Class acabou e me avisou", __ FUNCTION__);
}

void ) dealloc { - (Void) {dealloc
/ / Eu me retirar o recebimento das notificações relativas à
/ / Mensagem identificado com a string @ "operazioneTerminata"
defaultCenter ] removeObserver : self [[ NSNotificationCenter defaultCenter] removeObserver: self
"operazioneTerminata" Nome: @ "operazioneTerminata"
] ; objeto: nil];
; [Super dealloc];
}

@ End

Nomeando

Aqui, também, algumas palavras sobre a nomeação das notificações, sempre de acordo com as sugestões Apple. O nome da notificação, a qual é um NSString , é normalmente construído de acordo com este critério:

1
+ [ Did | Will ] + [ UniquePartOfName ] + Notification [Nome da classe Associado] + [DID | Will] + [UniquePartOfName] + Notificação

No exemplo acima, teríamos que colocar no arquivo H Classe B.:

1
2
3
4
5
6
7
8
9
10
/ / No arquivo de cabeçalho da Classe B
* const ClassBDidFinishedNotification; extern NSString * const ClassBDidFinishedNotification;

/ / No arquivo da Classe B implementação
const ClassBDidFinishedNotification = @ "ClassBDidFinishedNotification" ; NSString * const ClassBDidFinishedNotification = @ "ClassBDidFinishedNotification";

/ / O método na classe A, no entanto, é mais livre, mas poderia ser escrito
/ / Por exemplo, acrescentando "Notificação" no final - isso, no entanto, tem menos
/ / Importância
void ) finishedNotification; - (Void) finishedNotification;

Parâmetros

Ambas as técnicas descritas acima permite que você envie mensagens com um número arbitrário de parâmetros. Tomamos o exemplo acima e tentar passar dois parâmetros. No caso do protocolo / delegado, poderíamos ter:

1
2
3
4
5
@ Protocol ClassBDelegate <NSObject>
@ Opcional
/ / Mensagem enviada ao delegado quando a operação
void ) classB : ( ClassB * ) aClassB avvisoConID : ( int ) aID; - (Void) classB: (ClassB *) aClassB avvisoConID: (int) ajuda;
@ End

A implementação deve ter sido escrito:

1
2
3
4
5
6
7
8
void ) operazione { - (Void) {operação
/ / ... operação
/ / Se houver um delegado e delegado que está em conformidade com o protocolo
/ / ClassBDelegate enviar avisos de acabamento
delegate && [ delegate respondsToSelector : @selector ( classB : avvisoConID : ) ] ) { if (&& delegado [delegado respondsToSelector: @ selector (classB: avvisoConID :)]) {
self avvisoConID : 57 ] ; [ClassB delegado: self avvisoConID: 57];
}
}

Como você pode ver muito, muito simples.

) contiene un puntatore ad un NSDictionary con la collezione dei parametri. Para as notificações, o discurso é semelhante, embora aqui nós usamos um único parâmetro userInfo que normalmente (se não nil ), contém um ponteiro para um NSDictionary com a coleção de parâmetros. No momento em que a comunicação teria que:

1
2
3
4
5
6
7
8
void ) operazione { - (Void) {operação
/ / ... operação
/ / Envia uma notificação a todos aqueles que se inscreveram
defaultCenter ] postNotificationName : @ "operazioneTerminata" [[ NSNotificationCenter defaultCenter] postNotificationName: @ "operazioneTerminata"
objeto: self
NSDictionary dictionaryWithObject : [ NSNumber numberWithInt : 57 ] userInfo: [ NSDictionary dictionaryWithObject: [ NSNumber numberWithInt: 57]
"avvisoConID" ] ] ; forKey: @ "avvisoConID"]];
}

No momento da leitura de notificação:

1
2
3
4
5
6
7
void ) notificaOperazioneTerminata : ( NSNotification * ) aNotification { - (Void) notificaOperazioneTerminata: ( NSNotification *) {aNotification
/ / Método chamado pela Classe B
"%s: la Classe B ha terminato e mi ha avvisato!" , __FUNCTION__ ) ; NSLog (@ "% s: o B-Class acabou e me avisou", __ FUNCTION__);

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

Em conclusão, quando usar delegados / protocolos e notificações?

Como vimos ambas as soluções são simples, poderoso e fácil de implementar. Se os eventos na instância de nossa classe pode afetar vários objetos, não temos outra escolha a não ser usar notificações. Em todos os outros casos, os delegados / protocolos ou ambas as soluções.
Normalmente recomendamos a partir da implementação de protocolos e delegados, como se isso deve acontecer de que um evento é de interesse coletivo, sempre podem ser enviados na notificação.

5 comentários para: " "

  1. 11 março, 2011 Paul :

    Excelente artigo!
    Clara, precisa e cheia de exemplos!
    Blog muito interessante!
    Saudações

  2. 20 de novembro de 2011 iLeW:

    Não é que você poderia ter um último exemplo do uso de delegados? Aqui estamos limitados a definição, mas, em seguida, um exemplo prático de como usá-lo (referindo-se ao código no exemplo)?

  3. 24 de novembro de 2011 Objective-C: adenda sobre notificações e delegados - Undolog.com - Undolog.com :

    [...] A questão da ILeW com um artigo genuíno para explicar melhor, anexando exemplo, como eles funcionam e os delegados [...]

  4. 02 de fevereiro de 2012 louis:

    muito clara e simples
    Devo admitir que a escrita de um pa dificilmente usar delegados criados por mim ... é claro que é uma técnica que eu prometi a usar com mais freqüência, embora forçando a necessidade
    parabéns

Deixe um comentário

TAG XHTML permita: Código de acesso:
 <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