lunedì 19 aprile 2010

Guida Objective-C in Italiano - Parte 5 - I Protocolli


Siamo arrivati alla quinta parte della Guida all'Objective-C, negli articoli precedenti abbiamo parlato a lungo dei concetti base del linguaggio quali, gli Oggetti, le Classi, la definizione di Classi e Interfacce, e l'inizializzazione degli Oggetti. Oggi vi faremo luce su tutti gli aspetti di un utile strumento di questo linguaggio che è rappresentato dai Protocolli.

Ricordo che il presente post è una traduzione in italiano della Guida all'Objective C, presente sul sito developer.apple.com a questo link. L'Objective C è il linguaggio di programmazione per creare applicazioni per iPhone. Questa guida potrebbe interessare a tutti gli sviluppatori che vogliono imparare le basi di questo linguaggio che è il fondamento per programmare nuove applicazioni per Mac Os X, iPhone ed iPad.

Protocolli

I protocolli dichiarano metodi che possono essere implementati in ogni classe. I protocolli sono utili in almeno tre situazioni:
  • Per dichiarare metodi che altri pensano di implementare
  • Per dichiarare l'interfaccia a un oggetto e nasconderla alla sua classe
  • Per catturare similarità tra le classi che non sono correlate gerarchicamente.

Dichiarare Interfacce da far implementare agli altri

Classe e categoria di interfacce dichiarano metodi che sono associati con una classe particolare - metodi principali che la classe implementa. Protocolli formali e informali, d'altro canto, dichiarano metodi che sono indipendenti da qualunque classe specifica, ma che qualunque classe, e forse molte classi, potrebbero implementare.

Un protocollo è semplicemente una lista di dichiarazioni di metodi, non allegate ad una definizione di classe. Per esempio, questi metodi che riportano azioni dell'utente sul mouse, potrebbero essere raggruppati in un protocollo:
- (void)mouseDown:(NSEvent *)theEvent;

- (void)mouseDragged:(NSEvent *)theEvent;

- (void)mouseUp:(NSEvent *)theEvent;

Ogni classe che vuole rispondere ad eventi del mouse dovrebbe adottare il protocollo e implementare i suoi metodi.

I protocolli liberano le dichiarazioni dei metodi dalla dipendenza dalla gerarchia di classe, così che possono essere usate in modi in cui classi e categorie non possono. I protocolli elencano metodi che sono (o possono essere) implementati da qualche parte, ma l'identità della classe che li implementa non è di interesse. Cosa è di interesse o meno è una particolare classe conforme al protocollo - se ha implementato dei metodi che il protocollo dichiara. Così gli oggetti possono essere raggruppati in tipi, non solo su basi di somiglianza dovute al fatto che ereditano dalla stessa classe, ma anche sulla base della loro somiglianza in conformazione allo stesso protocollo. Le classi in rami non collegati della gerarchia ereditaria, potrebbero essere tipate ugualmente perché sono conformi allo stesso protocollo.

I protocolli possono giocare un ruolo significante nella progettazione OO, specialmente quando un progetto è diviso tra più implementatori o se incorpora oggetti sviluppati in altri progetti. Il software Cocoa usa pesantemente i protocolli per supportare la comunicazione tra i processi attraverso i messaggi Objective-C.

Comunque, un programma Objective-C non ha bisogno di usare protocolli. A differenza della definizione di classi e le espressioni di messaggi, sono opzionali. Alcuni frameworks Cocoa li usano; altri no. Dipende tutto dal compito attuale.

Metodi per gli altri da implementare.

Se conosci la classe di un oggetto, puoi guardare la sua dichiarazione di interfaccia ( e le dichiarazioni delle interfacce delle classi da cui eredita) per trovare a quali messaggi risponde. Queste dichiarazioni annunciano i messaggi che ricevono. I protocolli forniscono anche un modo per fargli annunciare i messaggi che inviano.

La comunicazione funziona in entrambi i modi; oggetti inviano messaggi così come lì ricevono. Ad esempio, un oggetto potrebbe delegare responsabilità per una certa operazione ad un altro oggetto, o potrebbe in occasione semplicemente aver bisogno di chiedere informazioni ad un altro oggetto. In alcuni casi, un oggetto potrebbe voler notificare ad altri oggetti le sue azioni così che loro possano prendere qualsiasi misura collaterale che potrebbe essere richiesta.

Se sviluppi la classe di un sender e la classe di un receiver come parte dello stesso progetto (o se qualcun altro ti ha fornito il ricevitore e i suoi file interfaccia), questa comunicazione è facilmente coordinata. Il sender semplicemente importa il file interfaccia del receiver. Il file importato dichiara i selettori di metodo che il sender usa nei messaggi che invia.

Comunque, se sviluppi un oggetto che invia messaggi a oggetti che non sono definiti - oggetti che stai lasciando agli altri da implementare - non vuoi avere il file di interfaccia del ricevente. Vi serve un altro modo di dichiarare i metodi che usate nei messaggi ma che non implementate. Un protocollo serve a questo scopo. Informa il compilatore riguardo ai metodi che la classe usa informa anche gli altri implementatori dei metodi che hanno bisogno di definire per avere i loro oggetti che lavorano con i tuoi.

Supponiamo, ad esempio, che sviluppi un oggetto che chiede l'assistenza di un altro oggetto inviandogli il messaggio helpOut: ed altri messaggi. Fornisci una variabile di istanza assistant per memorizzare l'outlet per questi messaggi e definire un metodo compagno per impostare la variabile di istanza. Questo metodo permette agli altri oggetti di registrarsi come potenziali destinatari per i messaggi del tuo oggetto:
- setAssistant:anObject

{

assistant = anObject;

}

Allora, ogni volta che un messaggio sta per essere inviato ad assistant, è fatto un controllo per accertarsi che il ricevente implementi un metodo che possa rispondere:
- (BOOL)doWork

{

...

if ( [assistant respondsToSelector:@selector(helpOut:)] ) {

[assistant helpOut:self];

return YES;

}

return NO;

}

Dato che, quando hai scritto questo codice, non potevi sapere che tipo di oggetti avrebbero potuto registrarsi come assistant, puoi solo dichiarare un protocollo per il metodo helpOut:; non puoi importare il file interfaccia della classe che lo implementa.

Dichiarare Interfacce per Oggetti Anonimi

Un protocollo può essere usato per dichiarare i metodi di un oggetto anonimo, un oggetto di classe sconosciuta. Un oggetto anonimo può rappresentare un servizio o gestire un insieme limitato di funzioni, specialmente dove serve un solo grande oggetto della sua classe. (Oggetti che giocano un ruolo fondamentale nel definire l'architettura di un'applicazione e oggetti che devi inizializzare prima di usare non sono buoni candidati per l'anonimità).

Gli oggetti non sono anonimi al loro sviluppatore, ovviamente, ma sono anonimi quando lo sviluppatore fornisce loro a qualcun altro. Ad esempio, considera le seguenti situazioni:
  • Qualcuno che fornisce un framework o una suite di oggetti ad altri da usare può includere oggetti che non sono identificati da un nome di classe o un file interfaccia. Mancando nome e interfaccia di classe, gli utenti non hanno modo di creare istanze della classe. Invece, il fornitore deve fornire un'istanza pronta fatta. Tipicamente, un metodo in un'altra classe restituisce un oggetto usabile:

    id formatter = [receiver formattingService];

  • 
L'oggetto restituito dal metodo è un oggetto senza un'identità di classe, almeno non uno dei fornitori è disposto a rivelare. Affinchè sia utile, il fornitore deve voler identificare almeno alcuni dei messaggi a cui esso può rispondere. Questo è fatto associando l'oggetto con una lista di metodi dichiarati in un protocollo.
  • Puoi inviare messaggi Objective-C a oggetti remoti - oggetti in altre applicazioni. Ogni applicazione ha la sua propria struttura, classi e logica interna. Ma non ti serve sapere come un'altra applicazione funziona o con cosa i suoi componenti stanno comunicando. Da straniero, tutto quello che devi sapere è quali messaggi puoi inviare (il protocollo) e dove inviarli (il ricevente). Un'applicazione che pubblica uno dei suoi oggetti come potenziale ricevente di messaggi remoti, deve anche pubblicare un protocollo che dichiara i metodi che l'oggetto userà per rispondere a quei messaggi. Non deve rivelare nient'altro che l'oggetto. L'applicazione che invia non ha bisogno di conoscere la classe dell'oggetto o usare la classe nel suo proprio design Tutto ciò che gli serve è il protocollo.
I protocolli rendono possibili gli oggetti anonimi. Senza un protocollo, non ci sarebbe modo di dichiarare un'interfaccia a un oggetto senza identificare la sua classe.
Nota: anche se il fornitore di un oggetto anonimo non rivela la sua classe, l'oggetto stesso si rivela a runtime. Un messaggio di classe restituisce la classe dell'oggetto anonimo. Comunque, c'è solitamente poco punto nel cercare queste informazioni extra; L'informazione nel protocollo è sufficiente.

Somiglianze Non-Gerarchiche

Se più di una classe implementasse un insieme di metodi, quelle classi sarebbero spesso raggruppate sotto una classe astratta che dichiara i metodi che hanno in comune. Ogni sottoclasse potrebbe re-implementare i metodi a modo proprio, ma la gerarchia ereditaria e la dichiarazione in comune nella classe astratta cattura la somiglianza essenziale tra le sottoclassi.
Comunque, a volte non è possibile raggruppare metodi in comune in una classe astratta. Le classi non correlate in molti aspetti, che non potrebbero tuttavia aver bisogno di implementare alcuni metodi simili. Questa somiglianza singolare potrebbe non giustificare una relazione gerarchica. Ad esempio, potresti volere aggiungere un supporto per creare una rappresentazione XML degli oggetti nella tua applicazione e per inizializzare oggetti da una rappresentazione XML:
- (NSXMLElement *)XMLRepresentation;

- initFromXMLRepresentation:(NSXMLElement *)xmlString;

Questi metodi potrebbero essere raggruppati in un protocollo e le somiglianze tra le classi che lo implementano stimate per annotare che sono tutte conformi allo stesso protocollo.

Gli oggetti possono essere tipati a seconda delle somiglianze (i protocolli a cui sono conformi), piuttosto che a seconda della loro classe. Ad esempio, un'istanza di NSMatrix deve comunicare con gli oggetti che rappresentano le sue celle. La matrice potrebbe richiedere che ognuno di questi oggetti sia un tipo di NSCell (una classe basata su tipo) e affidarsi al fatto che tutti gli oggetti che eredita dalla classe NSCell hanno metodi che hanno bisogno di rispondere a messaggi di NSMatrix. Alternativamente, l'oggetto NSMatrix potrebbe richiedere che oggetti rappresentanti celle abbiano metodi che possono a rispondere a un particolare insieme di messaggi (un tipo basato su protocollo). In questo caso, l'oggetto NSMatrix non si preoccuperebbe di a quale classe appartiene un oggetto cella, appena ha implementato i metodi.

Protocolli Formali

Il linguaggio Objective-C fornisce un modo di dichiarare formalmente una lista di metodi come un protocollo. I protocolli formali sono supportati dal linguaggio e dal sistema di runtime. Ad esempio, il compilatore può controllare i tipi basati su protocolli, e gli oggetti possono introspezionare a runtime per riportare se sono o non sono conformi a un protocollo.

Dichiarare un Protocollo

I protocolli formali si dichiarano con la direttiva @protocol :
@protocol ProtocolName

method declarations

@end

Ad esempio, puoi dichiarare un protocollo di rappresentazione XML in questo modo:
@protocol MyXMLSupport

- initFromXMLRepresentation:(NSXMLElement *)XMLElement;

- (NSXMLElement *)XMLRepresentation;

@end

Diversamente dai nomi di classi, i nomi dei protocolli non hanno visibilità globale. Vivono nel loro proprio namespace.

Metodi di Protocolli Opzionali

I metodi dei protocolli possono essere segnati come opzionali usando la parola chiave @optional. La parola chiave @required, corrisponde a @optional e denota formalmente la semantica del comportamento di default. Puoi usare @optional e @required per partizionare il tuo protocollo in sezioni. Se non specifichi alcuna parola chiave, è richiesto @required.
@protocol MyProtocol



- (void)requiredMethod;



@optional

- (void)anOptionalMethod;

- (void)anotherOptionalMethod;



@required

- (void)anotherRequiredMethod;



@end

Nota: In Mac OS X v10.5, i protocolli potrebbero non includere le proprietà dichiarate opzionali. Questo vincolo è rimosso da Mac OS X v10.6 in poi.

Protocolli Informali

In aggiunta ai protocolli formali, puoi anche definire un protocollo informale raggruppando i metodi in dichiarazioni di categoria:
@interface NSObject ( MyXMLSupport )

- initFromXMLRepresentation:(NSXMLElement *)XMLElement;

- (NSXMLElement *)XMLRepresentation;

@end

I protocolli informali sono tipicamente dichiarati come categorie della classe NSObject, dato che associano largamente i nomi dei metodi con qualunque classe che eredita da NSObject. Dato che tutte le classi ereditano dalla classe radice, i metodi non sono ristretti ad alcuna parte della gerarchia. (Potrebbe anche essere possibile dichiarare un protocollo informale come categoria di un'altra classe per limitarlo a un certo ramo della gerarchia, ma ci sono poche ragioni per farlo).

Una volta usato per dichiarare un protocollo, un'interfaccia di categoria non ha un'implementazione corrispondente. Invece, le classi che implementano il protocollo dichiarano ancora i metodi nei loro propri files di interfaccia e li definiscono insieme con gli altri metodi nei loro files di implementazione.

Un protocollo informale piega le regole delle dichiarazioni di categoria per elencare un gruppo di metodi ma non associarli ad alcuna classe o implementazione particolare.

Per essere informali, i protocolli dichiarati nelle categorie non ricevono molto supporto dal linguaggio. Non ci sono controlli di tipo a tempo di compilazione e neanche un controllo a runtime per vedere se un oggetto è conforme al protocollo. Per ottenere questi benefici, dovete usare un protocollo formale. Un protocollo informale potrebbe essere utile quando tutti i metodi sono opzionali, come per un delegato, ma è tipicamente meglio usare un protocollo formale con i metodi opzionali.

Oggetti dei Protocolli

Proprio come le classi sono rappresentate a runtime dagli oggetti della classe e i metodi dai codici selettori, i protocolli formali sono rappresentati da una speciale istanza-tipo di dati della classe Protocol. Il codice sorgente che si occupa del protocollo deve riferirsi all'oggetto di Protocol.

In molti modi, i protocolli sono simili alle definizioni di classe. Entrambi dichiarano metodi, e a runtime entrambi sono rappresentati da oggetti - classi da oggetti Class e protocolli da oggetti Protocol. Come gli oggetti di classe, gli oggetti di protocollo sono creati automaticamente dalle definizioni e le dichiarazioni trovate nel codice sorgente e sono usati dal sistema di runtime. Non sono allocati e inizializzati in codice sorgente programma.

Il codice sorgente può riferirsi a un oggetto protocollo usando la direttiva @protocol( ) - la stessa direttiva che dichiara un protocollo, tranne che qui ha un insieme di parentesi tonde. Le parentesi racchiudono il nome del protocollo:
Protocol *myXMLSupportProtocol = @protocol(MyXMLSupport);
Questo è l'unico modo per il codice sorgente di evocare un oggetto Protocol. Diversamente da un nome di classe, un nome di protocollo non designa l'oggetto - tranne dentro @protocol( ).

Il compilatore crea un oggetto Protocol per ogni dichiarazione di protocollo che incontra, ma solo se il protocollo è anche:
  • Adottato da una classe, o
  • Riferito a qualche punto nel codice sorgente (usando @protocol( ) )
I protocolli che sono dichiarati ma non usati (tranne per il controllo dei tipi descritto sotto) non sono rappresentati da oggetti Protocol a runtime.

Adottare un Protocollo

Adottare un protocollo è simile in alcuni modi a dichiarare una superclasse. Entrambi i metodi definiscono alla classe. La dichiarazione di superclasse gli assegna i metodi ereditati; Il protocollo gli assegna metodi dichiarati nella lista del protocollo. Si dice che una classe adotta un protocollo formale se nella sua dichiarazione, elenca il protocollo tra parentesi angolari dopo il nome della superclasse:
@interface ClassName : ItsSuperclass < protocol list >
Le categorie adottano protocolli più o meno allo stesso modo:
@interface ClassName ( CategoryName ) < protocol list >
Una classe può adottare più di un protocollo; i nomi nella lista dei protocolli sono separati da virgole.
@interface Formatter : NSObject < Formatting, Prettifying >
Una classe o categoria che adotta un protocollo deve implementare tutti i metodi richiesti che il protocollo dichiara, altrimenti il compilatore emetterà un warning La classe Formatter sopra, definirebbe tutti i metodi richiesti dichiarati nei due protocolli che adotta, in aggiunta a qualunque potrebbe dichiarare lui stesso.

Una classe o categoria che adotta un protocollo deve importare il file header dove il protocollo è dichiarato. I metodi dichiarati nel protocollo adottato non sono dichiarati altrove nell'interfaccia di classe o categoria.

é possibile per una classe adottare semplicemente protocolli e dichiarare nessun altro metodo. Ad esempio, la seguente dichiarazione di classe adotta i protocolli Formatti e Prettyfying, ma di per se non dichiara variabili di istanza o metodi:
@interface Formatter : NSObject < Formatting, Prettifying >

@end

Conformarsi a un Protocollo

Si dice che una classe è conforme a un protocollo formale se adotta il protocollo o eredita da altre classi che lo adottano. Un'istanza della classe è detta conforme allo stesso insieme di protocolli a cui è conforme la sua classe.

Dato che una classe deve implementare tutti i metodi richiesti dichiarati nel protocollo che adotta, dicendo che una classe o un'istanza è conforme a un protocollo è equivalente a dire che ha nel suo repertorio tutti i metodi che il protocollo dichiara.

È possibile controllare se un oggetto è conforme ad un protocollo inviandogli il messaggio conformsToProtocol:
if ( ! [receiver conformsToProtocol:@protocol(MyXMLSupport)] ) {

// Object does not conform to MyXMLSupport protocol

// If you are expecting receiver to implement methods declared in the

// MyXMLSupport protocol, this is probably an error

}

(Nota che c'è anche un metodo di classe con lo stesso nome —conformsToProtocol: .)

Il test conformsToProtocol: è come il test respondsToSelector: per un singolo metodo, tranne che testa se un protocollo è stato adottato ( e presumibilmente tutti i metodi che dichiara sono implementati) piuttosto che solo se un particolare metodo è stato implementato. Poiché controlla tutti i metodi, conformsToProtocol: può essere più efficiente rispetto a respondsToSelector:.

Il test conformsToProtocol: è anche come il test isKindOfClass:, tranne per il fatto che testa su un tipo basato su un protocollo piuttosto che su un tipo basato su gerarchia ereditaria.

Controlli di Tipo

Le dichiarazioni di tipo per gli oggetti possono essere estese per includere protocolli formali. I protocolli così offrono la possibilità di un altro livello di controllo di tipi da parte del compilatore, uno che è più astratto dato che non è legato ad una particolare implementazione.
In una dichiarazione di tipo, i nomi dei protocolli sono elencati tra parentesi angolari dopo il nome del tipo:
- (id )formattingService;

id anObject;

Proprio come la tipatura satica permette al compilatore di testare su un tipo basato su gerarchia di classi, questa sintassi permette al compilatore di testare su un tipo basato sulla conformità ad un protocollo.

Ad esempio, se Formatter è una classe astratta, questa dichiarazione
Formatter *anObject;
raggruppa tutti gli oggetti che ereditano da Formatter in un tipo, e permette al compilatore di controllare gli assegnamenti contro quel tipo.

Analogamente, questa dichiarazione,
id anObject;
raggruppa tutti gli oggetti conformi al protocollo Formatting in un tipo, senza tener conto della loro posizione nella gerarchia delle classi. Il compilatore può accertarsi che solo gli oggetti conformi al protocollo siano assegnati al tipo.

In ogni caso, il tipo raggruppa oggetti simili - in ogni caso, perché condividono un'eredità comune, o perché convergono ad un comune insieme di metodi.

I due tipi possono essere combinati in una dichiarazione singola:
Formatter *anObject;
I protocolli non possono essere usati per tipare gli oggetti delle classi. Solo le istanze possono essere tipate staticamente ad un protocollo, proprio come ogni istanza può essere staticamente tipata ad una classe. (comunque, a runtime, sia le classi che le istanze risponderanno ai messaggi conformsToProtocol:).

Protocolli all'interno di Protocolli

Un protocollo può incorporare altri protocolli usando la stessa sintassi che le classi usano per adottare un protocollo:
@protocol ProtocolName < protocol list >
Tutti i protocolli elencati tra le parentesi angolari sono considerati parte del protocollo ProtocolName. Ad esempio se il protocollo Paging incorpora il protocollo Formatting,
@protocol Paging < Formatting >

qualsiasi oggetto conforme al protocollo Paging è conforme anche a Formatting. Dichiarazione di tipi
id someObject;
e messaggi conformsToProtocol:
if ( [anotherObject conformsToProtocol:@protocol(Paging)] )

...

hanno bisogno di menzionare solo il protocollo Paging per testare anche la conformità di Formatting.

Quando una classe adotta un protocollo, deve implementare i metodi richiesti che il protocollo dichiara, come abbiamo detto prima. In aggiunta, deve conformarsi a qualunque protocollo che il protocollo adottato incorpora. Se un protocollo incorporato incorpora ancora altri protocolli, la classe deve anche conformarsi a loro. Una classe può conformarsi ad un protocollo incorporato tramite o uno o l'altro di questi modi:
  • Implementando i metodi che il protocollo dichiara, o
  • ereditando da una classe che adotta il protocollo ed implementa i metodi.
Supponiamo, ad esempio, che la classe Pager adotti il protocollo Paging. Se Pager fosse una sottoclasse di NSObject,
@interface Pager : NSObject < Paging >
dovrebbe implementare tutti i metodi di Paging, inclusi quelli dichiarati nel protocollo incorporato Formatting. Adotta il protocollo Formatting insieme con Paging.

D'altro lato, se Pager è una sottoclasse di Formatter (una classe che indipendentemente adotta il protocollo Formatting),
@interface Pager : Formatter < Paging >
deve implementare adeguatamente tutti i metodi dichiarati nel protocollo Paging, ma non quelli dichiarati in Formatting. Pager eredita la conformità al protocollo Formatting da Formatter.

Nota che una classe può essere conforme a un protocollo senza adottarlo formalmente, semplicemente implementando i metodi dichiarati nel protocollo.

Referring to Other Protocols

Quando lavorando su applicazioni complesse, occasionalmente vi troverete a scrivere codice che assomiglia a questo:
#import "B.h"



@protocol A

- foo:(id <B>)anObject;

@end

dove il protocollo B è dichiarato così:
#import "A.h"



@protocol B

- bar:(id <A>)anObject;

@end

In una tale situazione, risulta la circolarità e nessun file compilerà correttamente. Per rompere questo ciclo ricorsivo, devi usare la direttiva @protocol per fare un riferimento anticipato al protocollo richiesto invece di importare il file interfaccia dove è definito il protocollo. Il seguente codice illustra come potresti farlo:
@protocol B;



@protocol A

- foo:(id <B>)anObject;

@end

Nota che usando la direttiva @protocol in questa maniera semplicemente si informa il compilatore che "B" è un protocollo che sarà definito successivamente. Non importa il file interfaccia dove è definito il protocollo B.

Fine quinta parte

E siamo alla fine del quinto articolo di questa Guida dedicato ai Protocolli del linguaggio di programmazione Objective-C. Spero col tempo di realizzare un'utile manuale disponibile a tutti. Il prossimo post di questa guida, tratterà la dichiarazione e l'implementazione di proprietà. Segnalatemi eventuali errori, o commentate l'articolo se l'avete trovato utile, anche per incentivarmi a continuare a pubblicare le mie traduzioni. Iscrivetevi ai feed del blog per essere sempre aggiornati automaticamente ogni volta che sono disponibili nuovi contenuti.


Nessun commento:

Posta un commento

Related Posts with Thumbnails