In questo capitolo, esploreremo i metodi Objective-C in modo molto più dettagliato di quello che abbiamo nei capitoli precedenti. Ciò include una discussione approfondita sui metodi di istanza, i metodi di classe, i metodi incorporati importanti, l'ereditarietà, le convenzioni di denominazione e i modelli di progettazione comuni.
Abbiamo lavorato con entrambi i metodi di istanza e classe in questo libro, ma prendiamoci un momento per formalizzare le due principali categorie di metodi in Objective-C:
Come abbiamo visto molte volte, i metodi di istanza sono denotati da un trattino prima del nome del metodo, mentre i metodi di classe sono preceduti da un segno più. Ad esempio, prendiamo una versione semplificata del nostro Person.h
file:
@interface Person: NSObject @property (copy) NSString * nome; - (vuoto) say Hello; + (Persona *) personWithName: (NSString *) nome; @fine
Allo stesso modo, anche i corrispondenti metodi di implementazione devono essere preceduti da un trattino o da un segno più. Quindi, un minimo Person.m
potrebbe sembrare qualcosa di simile:
#import "Person.h" @implementation Person @synthesize name = _name; - (vuoto) sayHello NSLog (@ "HELLO"); + (Person *) personWithName: (NSString *) name Person * person = [[Person alloc] init]; person.name = nome; persona di ritorno; @fine
Il di Ciao
il metodo può essere chiamato da casi del Persona
classe, mentre il personWithName
il metodo può essere chiamato solo dalla classe stessa:
Persona * p1 = [Person personWithName: @ "Frank"]; // Metodo di classe. [p1 sayHello]; // Metodo di istanza.
La maggior parte di questo dovrebbe esservi familiare ormai, ma ora abbiamo l'opportunità di parlare di alcune delle convenzioni esclusive in Objective-C.
In qualsiasi ambiente orientato agli oggetti, è importante poter accedere ai metodi dalla classe genitore. Objective-C utilizza uno schema molto simile a C #, tranne che in sostituzione di base
, usa il super
parola chiave. Ad esempio, la seguente implementazione di di Ciao
mostrerebbe CIAO
nel pannello di output, quindi chiamare la versione della classe genitore di di Ciao
:
- (vuoto) sayHello NSLog (@ "HELLO"); [super sayHello];
A differenza di C #, i metodi di sovrascrittura non devono essere esplicitamente contrassegnati come tali. Vedrai questo con entrambi i dentro
e dealloc
metodi discussi nella sezione seguente. Anche se questi sono definiti sul NSObject
classe, il compilatore non si lamenta quando si crea il proprio dentro
e dealloc
metodi in sottoclassi.
I metodi di inizializzazione sono necessari per tutti gli oggetti: un oggetto appena assegnato non è considerato "pronto all'uso" finché non è stato chiamato uno dei suoi metodi di inizializzazione. Sono il posto dove impostare valori predefiniti per variabili di istanza e altrimenti impostare lo stato dell'oggetto. Il NSObject
la classe definisce un valore predefinito dentro
metodo che non fa nulla, ma è spesso utile crearne di propri. Ad esempio, un'abitudine dentro
implementazione per il nostro Nave
class potrebbe assegnare valori predefiniti a una variabile di istanza chiamata _ammo
:
- (id) init self = [super init]; if (self) _ammo = 1000; return self;
Questo è il modo canonico per definire un'abitudine dentro
metodo. Il se stesso
la parola chiave è l'equivalente di C # Questo
-è usato per fare riferimento all'istanza che chiama il metodo, che consente a un oggetto di inviare messaggi a se stesso. Come puoi vedere, tutto dentro
i metodi sono necessari per restituire l'istanza. Questo è ciò che rende possibile usare il [[Allocazione nave] init]
sintassi per assegnare l'istanza a una variabile. Notare anche questo perché il NSObject
l'interfaccia dichiara il dentro
metodo, non è necessario aggiungere un dentro
dichiarazione a Ship.h
.
Mentre semplice dentro
metodi come quello mostrato nell'esempio precedente sono utili per impostare valori di variabile di istanza predefiniti, spesso è più comodo passare i parametri a un metodo di inizializzazione:
- (id) initWithAmmo: (unsigned int) theAmmo self = [super init]; if (self) _ammo = theAmmo; return self;
Se provieni da uno sfondo C #, potresti essere a disagio con il initWithAmmo
nome del metodo. Probabilmente ti aspetteresti di vedere il Munizioni
parametro separato dal nome del metodo effettivo come void init (uint ammo)
; tuttavia, la denominazione del metodo Objective-C si basa su una filosofia completamente diversa.
Ricordiamo che l'obiettivo di Objective-C è quello di forzare un'API a essere il più descrittiva possibile, assicurando che non vi sia assolutamente alcuna confusione su ciò che una chiamata al metodo sta per fare. Non puoi pensare ad un metodo come entità separata dai suoi parametri: sono una singola unità. Questa decisione progettuale si riflette in realtà nell'implementazione di Objective-C, che non fa distinzione tra un metodo e i suoi parametri. Internamente, un nome di metodo è in realtà il elenco dei parametri concatenati.
Ad esempio, considerare le seguenti tre dichiarazioni di metodo. Si noti che il secondo e il terzo non sono metodi incorporati di NSObject
, quindi tu fare è necessario aggiungerli all'interfaccia di classe prima di implementarli.
- (Id) init; - (id) initWithAmmo: (unsigned int) theMod; - (id) initWithAmmo: (unsigned int) theAmmo captain: (Person *) theCaptain;
Mentre questo sembra sovraccarico di metodo, tecnicamente non lo è. Queste non sono variazioni sul dentro
metodo: sono tutti metodi completamente indipendenti con nomi di metodi distinti. I nomi di questi metodi sono i seguenti:
init initWithAmmo: initWithAmmo: capitano:
Questa è la ragione per cui vedi la notazione indexOfObjectWithOptions: passingTest:
e indexOfObjectAtIndexes: opzioni: passingTest:
per riferirsi ai metodi nella documentazione ufficiale di Objective-C (presa da NSArray).
Da un punto di vista pratico, ciò significa che il primo parametro dei metodi dovrebbe sempre essere descritto dal nome del metodo "primario". Metodi ambigui come i seguenti sono generalmente disapprovati dai programmatori Objective-C:
- (id) shoot: (Ship *) aShip;
Invece, dovresti usare una preposizione per includere il primo parametro nel nome del metodo, in questo modo:
- (id) shootOtherShip: (Ship *) aShip;
Compresi entrambi OtherShip
e una nave
nella definizione del metodo può sembrare ridondante, ma ricorda che il una nave
l'argomento è usato solo internamente. Qualcuno che chiama il metodo sta per scrivere qualcosa di simile shootOtherShip: discoveryOne
, dove discoveryOne
è la variabile che contiene la nave che vuoi sparare. Questo è esattamente il tipo di verbosità che gli sviluppatori di Objective-C cercano.
In aggiunta a dentro
metodo per l'inizializzazione casi, Objective-C offre anche un modo per impostare classi. Prima di chiamare qualsiasi metodo di classe o creare un'istanza di oggetti, il runtime Objective-C chiama il inizializzare
metodo di classe della classe in questione. Questo ti dà l'opportunità di definire qualsiasi variabile statica prima che qualcuno usi la classe. Uno dei casi d'uso più comuni è la creazione di singleton:
Nave statica * _sharedShip; + (void) initialize if (self == [Ship class]) _sharedShip = [[auto allocazione] init]; + (Spedisci *) sharedShip return _sharedShip;
Prima della prima volta [Ship sharedShip]
viene chiamato, il runtime chiamerà [Ship initialize]
, che assicura che il singleton sia definito. Il modificatore di variabili statiche ha lo stesso scopo di C # -it crea una variabile a livello di classe invece di una variabile di istanza. Il inizializzare
il metodo viene chiamato una sola volta, ma viene chiamato su tutte le super classi, quindi è necessario fare attenzione a non inizializzare le variabili a livello di classe più volte. Questo è il motivo per cui abbiamo incluso il auto == [classe nave]
condizionale per essere sicuri _shareShip
è assegnato solo nel Nave
classe.
Si noti inoltre che all'interno di un metodo di classe, il se stesso
parola chiave si riferisce alla classe stessa, non a un'istanza. Così, [auto allocazione]
nell'ultimo esempio è l'equivalente di [Allocazione della nave]
.
La controparte logica del metodo di inizializzazione di un'istanza è il dealloc
metodo. Questo metodo viene chiamato su un oggetto quando il suo conteggio dei riferimenti raggiunge lo zero e la sua memoria sottostante sta per essere deallocata.
Se si utilizza la gestione manuale della memoria (scelta non consigliata), è necessario rilasciare qualsiasi variabile di istanza assegnata al proprio oggetto dealloc
metodo. Se non si liberano le variabili di istanza prima che il proprio oggetto vada fuori ambito, si avranno dei puntatori penzolanti sulle variabili di istanza, il che significa che la memoria trapelata ogni volta che viene rilasciata un'istanza della classe. Ad esempio, se il nostro Nave
classe ha assegnato una variabile chiamata _pistola
nel suo dentro
metodo, dovresti rilasciarlo dealloc
. Questo è dimostrato nel seguente esempio (Gun.h
contiene un'interfaccia vuota che definisce semplicemente il Pistola
classe):
#import "Ship.h" #import "Gun.h" @implementation Ship BOOL _gunIsReady; Gun * _gun; - (id) init self = [super init]; if (self) _gun = [[Gun alloc] init]; return self; - (void) dealloc NSLog (@ "Deallocating a Ship"); [_gun release]; [super dealloc]; @fine
Puoi vedere il dealloc
metodo in azione creando un Nave
e rilasciandolo, in questo modo:
int main (int argc, const char * argv []) @autoreleasepool Ship * ship = [[Ship alloc] init]; [nave autorelease]; NSLog (@ "La nave dovrebbe ancora esistere in autoreleasepool"); NSLog (@ "La nave dovrebbe essere deallocata da ora"); ritorno 0;
Ciò dimostra anche come funzionano gli oggetti con rilascio automatico. Il dealloc
il metodo non sarà chiamato fino alla fine del @autoreleasepool
blocco, quindi il codice precedente dovrebbe produrre quanto segue:
La nave dovrebbe ancora esistere in autoreleasepool Deallocating a Ship Ship dovrebbe essere deallocata ormai
Si noti che il primo NSLog ()
messaggio in principale()
È visualizzato prima quello nel dealloc
metodo, anche se è stato chiamato dopo il autorelease
chiamata.
Tuttavia, se si utilizza il conteggio dei riferimenti automatici, tutte le variabili di istanza verranno deallocate automaticamente e [super dealloc]
sarà chiamato anche per te (non dovresti mai chiamarlo esplicitamente). Quindi, l'unica cosa di cui ti devi preoccupare sono le variabili non object come i buffer creati con C's malloc ()
.
Piace dentro
, non devi implementare a dealloc
metodo se il tuo oggetto non ha bisogno di alcuna gestione speciale prima di essere rilasciato. Questo è spesso il caso per gli ambienti di conteggio automatico di riferimento.
Un grosso ostacolo per gli sviluppatori C # che stanno passando a Objective-C è l'apparente mancanza di metodi privati. A differenza di C #, tutti i metodi in una classe Objective-C sono accessibili a terze parti; tuttavia, è possibile emulare il comportamento dei metodi privati.
Ricorda che i client importano solo l'interfaccia di una classe (cioè i file di intestazione) - non dovrebbero mai vedere l'implementazione sottostante. Quindi, aggiungendo nuovi metodi all'interno del implementazione file senza includerli nel file interfaccia, possiamo nascondere efficacemente i metodi da altri oggetti. Anche se questo è più basato sulla convenzione rispetto ai "veri" metodi privati, ma è essenzialmente la stessa funzionalità: provare a chiamare un metodo che non è dichiarato in un'interfaccia comporterà un errore del compilatore.
Tentativo di chiamare un metodo "privato"Ad esempio, supponiamo di aver bisogno di aggiungere un privato prepareToShoot
metodo per il Nave
classe. Tutto ciò che devi fare è ometterlo Ship.h
mentre lo aggiungo a Ship.m
:
// Ship.h @interface Nave: NSObject @property (debole) Persona * capitano; - (vuoto) sparare; @fine
Questo dichiara un metodo pubblico chiamato sparare
, che userà il privato prepareToShoot
metodo. L'implementazione corrispondente potrebbe assomigliare a qualcosa:
// Ship.m #import "Ship.h" @implementation Ship BOOL _gunIsReady; @synthesize captain = _captain; - (void) shoot if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = SÌ; NSLog (@ "Firing!"); - (void) prepareToShoot // Esegue alcune funzionalità private. NSLog (@ "Preparazione dell'arma principale ..."); @fine
A partire da Xcode 4.3, è possibile definire metodi privati in qualunque posto nell'implementazione. Se si utilizza il metodo privato prima che il compilatore lo abbia visto (come nell'esempio precedente), il compilatore verifica il resto del blocco di implementazione per la definizione del metodo. Prima di Xcode 4.3, dovevi definire un metodo privato prima è stato utilizzato altrove nel file o inoltrato-dichiararlo con a estensione di classe.
Le estensioni di classe sono un caso speciale di categorie, che sono presentati nel prossimo capitolo. Così come non c'è modo di contrassegnare un metodo come privato, non c'è modo di contrassegnare un metodo come protetto; tuttavia, come vedremo nel prossimo capitolo, le categorie forniscono una potente alternativa ai metodi protetti.
I selettori sono il modo in cui Objective-C rappresenta i metodi. Ti permettono di "selezionare" dinamicamente uno dei metodi di un oggetto, che può essere usato per riferirsi a un metodo in fase di esecuzione, passare un metodo a un'altra funzione e capire se un oggetto ha un metodo particolare. Ai fini pratici, puoi pensare a un selettore come nome alternativo per un metodo.
Rappresentazione degli sviluppatori di un metodo rispetto alla rappresentazione di Objective-CInternamente, Objective-C utilizza un numero univoco per identificare ogni nome di metodo utilizzato dal programma. Ad esempio, un metodo chiamato di Ciao
potrebbe tradurre in 4984331082
. Questo identificatore è chiamato a selettore, ed è un modo molto più efficiente per il compilatore di riferirsi ai metodi rispetto alla loro rappresentazione a stringa intera. È importante capire che un selettore rappresenta solo il metodo nome-non una specifica implementazione del metodo. In altre parole, a di Ciao
metodo definito dal Persona
la classe ha lo stesso selettore di a di Ciao
metodo definito dal Nave
classe.
I tre strumenti principali per lavorare con i selettori sono:
@selettore()
- Restituisce il selettore associato a un nome del metodo del codice sorgente.NSSelectorFromString ()
- Restituisce il selettore associato alla rappresentazione stringa di un nome di metodo. Questa funzione consente di definire il nome del metodo in fase di esecuzione, ma è meno efficiente di @selettore()
.NSStringFromSelector ()
- Restituisce la rappresentazione della stringa di un nome di metodo da un selettore.Come puoi vedere, ci sono tre modi per rappresentare un nome di metodo in Objective-C: come codice sorgente, come stringa o come selettore. Queste funzioni di conversione sono mostrate graficamente nella seguente figura:
Conversione tra codice sorgente, stringhe e selettoriI selettori sono memorizzati in un tipo di dati speciale chiamato SEL
. Il seguente frammento mostra l'utilizzo di base delle tre funzioni di conversione mostrate nella figura precedente:
int main (int argc, const char * argv []) @autoreleasepool SEL selector = @selector (sayHello); NSLog (@ "% @", NSStringFromSelector (selettore)); if (selector == NSSelectorFromString (@ "sayHello")) NSLog (@ "I selettori sono uguali!"); restituisce 0;
Innanzitutto, usiamo il @selettore()
direttiva per capire il selettore per un metodo chiamato di Ciao
, che è una rappresentazione del codice sorgente di un nome di metodo. Nota che puoi passare qualunque nome del metodo a @selettore()
-non deve esistere altrove nel tuo programma. Successivamente, usiamo il NSStringFromSelector ()
funzione per convertire il selettore in una stringa in modo che possiamo visualizzarlo nel pannello di output. Infine, il condizionale mostra che i selettori hanno una corrispondenza uno-a-uno con i nomi dei metodi, indipendentemente dal fatto che li trovi tramite nomi di metodi o stringhe codificati.
L'esempio precedente utilizza un metodo semplice che non accetta parametri, ma è importante essere in grado di aggirare i metodi fare accettare parametri. Ricorda che il nome di un metodo consiste nel nome del metodo principale concatenato con tutti i nomi dei parametri. Ad esempio, un metodo con il firma
- (vuoto) sayHelloToPerson: (Person *) aPerson withGreeting: (NSString *) aGreeting;
avrebbe un metodo nome di:
sayHelloToPerson: withGreeting:
Questo è ciò che vorresti passare @selettore()
o NSSelectorFromString ()
per restituire l'identificatore per quel metodo. I selettori funzionano solo con il metodo nomi (non le firme), quindi non c'è non una corrispondenza uno-a-uno tra selettori e firme. Di conseguenza, il metodo nome nell'ultimo esempio corrisponderà anche una firma con diversi tipi di dati, inclusi i seguenti:
- (vuoto) sayHelloToPerson: (NSString *) aName withGreeting: (BOOL) useGreeting;
La verbosità delle convenzioni di denominazione di Objective-C evita le situazioni più confuse; tuttavia, i selettori per i metodi a un parametro possono essere ancora difficili perché aggiungere due punti al nome del metodo lo trasforma in a completamente differente metodo. Ad esempio, nel seguente esempio, il primo nome del metodo non accetta un parametro, mentre il secondo esegue:
dì Ciao
Ancora una volta, le convenzioni di denominazione fanno molto per eliminare la confusione, ma è comunque necessario accertarsi di sapere quando è necessario aggiungere due punti alla fine del nome di un metodo. Questo è un problema comune se sei nuovo ai selettori e può essere difficile eseguire il debug, poiché i due punti finali creano ancora un nome di metodo perfettamente valido.
Ovviamente, registrando un selettore in a SEL
la variabile è relativamente inutile senza la possibilità di eseguirla in seguito. Dal momento che un selettore è solo un metodo nome (non un'implementazione), deve sempre essere associato a un oggetto prima di poterlo chiamare. Il NSObject
la classe definisce a performSelector:
metodo per questo scopo.
[joe performSelector: @selector (sayHello)];
Questo è l'equivalente di chiamare di Ciao
direttamente joe
:
[joe sayHello];
Per i metodi con uno o due parametri, è possibile utilizzare il relativo performSelector: withObject:
e performSelector: withObject: withObject:
metodi. La seguente implementazione del metodo:
- (vuoto) sayHelloToPerson: (Person *) aPerson NSLog (@ "Hello,% @", [nome di una persona]);
potrebbe essere chiamato dinamicamente passando il una persona
argomento al performSelector: withObject:
metodo, come dimostrato qui:
[joe performSelector: @selector (sayHelloToPerson :) withObject: bill];
Questo è l'equivalente del passaggio del parametro direttamente al metodo:
[joe sayHelloToPerson: bill];
Allo stesso modo, il performSelector: withObject: withObject:
metodo consente di passare due parametri al metodo di destinazione. L'unico avvertimento è che tutti i parametri e il valore di ritorno del metodo devono essere oggetti - non funzionano con tipi di dati C primitivi come int
, galleggiante
, ecc. Se hai bisogno di questa funzionalità, puoi inserire il tipo primitivo in una delle numerose classi wrapper di Objective-C (ad es.., NSNumber
) o utilizzare l'oggetto NSInvocation per incapsulare una chiamata di metodo completa.
Non è possibile eseguire un selettore su un oggetto che non ha definito il metodo associato. Ma a differenza delle chiamate al metodo statico, non è possibile determinare in fase di compilazione se performSelector:
genererà un errore. Invece, è necessario verificare se un oggetto può rispondere a un selettore in fase di esecuzione utilizzando il nome corretto respondsToSelector:
metodo. Ritorna semplicemente SÌ
o NO
a seconda che l'oggetto possa eseguire il selettore:
SEL methodToCall = @selector (sayHello); if ([joe respondsToSelector: methodToCall]) [joe performSelector: methodToCall]; else NSLog (@ "Joe non sa come eseguire% @.", NSStringFromSelector (methodToCall));
Se i tuoi selettori vengono generati dinamicamente (ad esempio, se methodToCall
è selezionato da un elenco di opzioni) o non hai il controllo sull'oggetto target (ad es., joe
può essere uno dei diversi tipi di oggetti), è importante eseguire questo controllo prima di provare a chiamare performSelector:
.
L'idea che sta alla base dei selettori è quella di essere in grado di aggirare i metodi proprio come si passa intorno agli oggetti. Questo può essere usato, ad esempio, per definire dinamicamente una "azione" per a Persona
oggetto da eseguire più avanti nel programma. Ad esempio, considera la seguente interfaccia:
Esempio di codice incluso: selettori
@interface Person: NSObject @property (copy) NSString * nome; @property (debole) Persona * amico; @property SEL azione; - (vuoto) say Hello; - (vuoto) sayGoodbye; - (vuoto) coerceFriend; @fine
Insieme all'attuazione corrispondente:
#import "Person.h" @implementation Person @synthesize name = _name; @synthesize friend = _friend; @synthesize action = _action; - (void) sayHello NSLog (@ "Hello, says% @.", _name); - (void) sayGoodbye NSLog (@ "Arrivederci, dice% @.", _name); - (void) coerceFriend NSLog (@ "% @ sta per fare% @ fare qualcosa.", _nome, [_friend name]); [_friend performSelector: _action]; @fine
Come puoi vedere, chiama il coerceFriend
il metodo costringerà a diverso oggetto di eseguire alcune azioni arbitrarie. Ciò ti consente di configurare un'amicizia e un comportamento nella fase iniziale del tuo programma e attendere che si verifichi un evento particolare prima di attivare l'azione:
#importare#import "Person.h" NSString * askUserForAction () // Nel mondo reale, questo sarebbe catturare alcuni // input dell'utente per determinare quale metodo chiamare. NSString * theMethod = @ "sayGoodbye"; restituire il metodo; int main (int argc, const char * argv []) @autoreleasepool // Crea una persona e determina un'azione da eseguire. Person * joe = [[Person alloc] init]; joe.name = @ "Joe"; Person * bill = [[Person alloc] init]; bill.name = @ "Bill"; joe.friend = bill; joe.action = NSSelectorFromString (askUserForAction ()); // Attendi un evento ... // Esegui l'azione. [joe coerceFriend]; restituisce 0;
Questo è quasi esattamente il modo in cui vengono implementati i componenti dell'interfaccia utente in iOS. Ad esempio, se avessi un pulsante, lo configureresti con un oggetto target (ad es., amico
) e un'azione (ad es., azione
). Quindi, quando l'utente alla fine preme il pulsante, può utilizzarlo performSelector:
per eseguire il metodo desiderato sull'oggetto appropriato. Permettendo sia l'oggetto e il metodo per variare in modo indipendente offre una flessibilità significativa: il pulsante potrebbe letteralmente eseguire qualsiasi azione con qualsiasi oggetto senza alterare in alcun modo la classe del pulsante. Questo costituisce anche la base del modello di progettazione Target-Action, che è fortemente utilizzato nel iOS in modo succinto libro di accompagnamento.
In questo capitolo, abbiamo trattato i metodi di istanza e classe, insieme ad alcuni dei più importanti metodi incorporati. Abbiamo lavorato a stretto contatto con i selettori, che sono un modo per fare riferimento ai nomi dei metodi come codice sorgente o stringhe. Abbiamo anche presentato brevemente il modello di design Target-Action, che è un aspetto integrale della programmazione di iOS e OS X..
Il prossimo capitolo discute un modo alternativo per creare metodi privati e protetti in Objective-C.
Questa lezione rappresenta un capitolo di Objective-C, un eBook gratuito del team di Syncfusion.