Le categorie sono una funzione del linguaggio Objective-C che consente di aggiungere nuovi metodi a una classe esistente, proprio come le estensioni C #. Tuttavia, non confondere le estensioni C # con le estensioni Objective-C. Le estensioni di Objective-C sono un caso speciale di categorie che ti permettono di definire metodi che devono essere dichiarati nel blocco di implementazione principale.
Queste sono potenti funzionalità che hanno molti potenziali usi. Innanzitutto, le categorie consentono di suddividere un'interfaccia e un'implementazione di classe in diversi file, che fornisce la modularità necessaria per i progetti più grandi. In secondo luogo, le categorie consentono di correggere i bug in una classe esistente (ad es., NSString
) senza la necessità di sottoclasse. In terzo luogo, forniscono un'alternativa efficace ai metodi protetti e privati che si trovano in C # e in altre lingue simili a Simula.
UN categoria è un gruppo di metodi correlati per una classe e tutti i metodi definiti in una categoria sono disponibili attraverso la classe come se fossero definiti nel file di interfaccia principale. Ad esempio, prendi il Persona
classe con cui abbiamo lavorato durante questo libro. Se questo fosse un grande progetto, Persona
può avere dozzine di metodi che vanno dai comportamenti di base alle interazioni con altre persone al controllo dell'identità. L'API potrebbe richiedere che tutti questi metodi siano disponibili attraverso una singola classe, ma è molto più semplice per gli sviluppatori mantenere se ogni gruppo è memorizzato in un file separato. Inoltre, le categorie eliminano la necessità di ricompilare l'intera classe ogni volta che si modifica un singolo metodo, che può essere un risparmio di tempo per progetti molto grandi.
Diamo un'occhiata a come le categorie possono essere utilizzate per raggiungere questo obiettivo. Iniziamo con una normale interfaccia di classe e un'implementazione corrispondente:
// Person.h @interface Persona: NSObject @interface Persona: NSObject @property (readonly) NSMutableArray * friends; @property (copy) NSString * nome; - (vuoto) say Hello; - (vuoto) sayGoodbye; @end // Person.m #import "Person.h" @implementation Person @synthesize name = _name; @synthesize friends = _friends; - (id) init self = [super init]; if (self) _friends = [[NSMutableArray alloc] init]; return self; - (void) sayHello NSLog (@ "Hello, says% @.", _name); - (void) sayGoodbye NSLog (@ "Arrivederci, dice% @.", _name); @fine
Niente di nuovo qui, solo a Persona
classe con due proprietà (il amici
proprietà sarà utilizzata dalla nostra categoria) e due metodi. Successivamente, useremo una categoria per memorizzare alcuni metodi per interagire con altri Persona
le istanze. Crea un nuovo file, ma invece di una classe, usa il Categoria Objective-C modello. Uso Relazioni per il nome della categoria e Persona per il Categoria attiva campo:
Come previsto, questo creerà due file: un'intestazione per contenere l'interfaccia e un'implementazione. Tuttavia, entrambi sembreranno leggermente diversi da quelli con cui abbiamo lavorato. Per prima cosa, diamo un'occhiata all'interfaccia:
// Person + Relations.h #import#import "Person.h" @interface Person (Relations) - (void) addFriend: (Person *) aFriend; - (void) removeFriend: (Person *) aFriend; - (vuoto) sayHelloToFriends; @fine
Invece del normale @interfaccia
dichiarazione, includiamo il nome della categoria tra parentesi dopo il nome della classe che stiamo estendendo. Un nome di categoria può essere qualsiasi cosa, purché non sia in conflitto con altre categorie per la stessa classe. Di una categoria file nome dovrebbe essere il nome della classe seguito da un segno più, seguito dal nome della categoria (ad es., Persona + Relations.h
).
Quindi, questo definisce l'interfaccia della nostra categoria. Tutti i metodi che aggiungiamo qui saranno aggiunti all'originale Persona
classe in fase di esecuzione. Apparirà come se il Aggiungi amico:
, Rimuovi amico:
, e sayHelloToFriends
i metodi sono tutti definiti in Person.h
, ma possiamo mantenere la nostra funzionalità incapsulata e mantenibile. Si noti inoltre che è necessario importare l'intestazione per la classe originale, Person.h
. L'implementazione della categoria segue uno schema simile:
// Person + Relations.m #import "Person + Relations.h" @implementation Person (Relations) - (void) addFriend: (Person *) aFriend [[self friends] addObject: aFriend]; - (void) removeFriend: (Person *) aFriend [[self friends] removeObject: aFriend]; - (void) sayHelloToFriends for (Person * friend in [self friends]) NSLog (@ "Ciao,% @!", [nome amico]); @fine
Questo implementa tutti i metodi in Persona + Relations.h
. Proprio come l'interfaccia della categoria, il nome della categoria appare tra parentesi dopo il nome della classe. Il nome della categoria nell'implementazione deve corrispondere a quello nell'interfaccia.
Inoltre, si noti che non è possibile definire proprietà aggiuntive o variabili di istanza in una categoria. Le categorie devono fare riferimento ai dati memorizzati nella classe principale (amici
in questo caso).
È anche possibile sovrascrivere l'implementazione contenuta in Person.m
semplicemente ridefinendo il metodo in Persona + Relations.m
. Questo può essere usato per modificare patch di una classe esistente; tuttavia, non è consigliabile se si dispone di una soluzione alternativa al problema, poiché non ci sarebbe modo di ignorare l'implementazione definita dalla categoria. Vale a dire, a differenza della gerarchia di classi, le categorie sono una struttura organizzativa piatta: se si implementa lo stesso metodo in due categorie separate, è impossibile per il runtime determinare quale utilizzare.
L'unica modifica che devi fare per usare una categoria è importare il file di intestazione della categoria. Come puoi vedere nel seguente esempio, il Persona
la classe ha accesso ai metodi definiti in Person.h
insieme a quelli definiti nella categoria Persona + Relations.h
:
// main.m #import#import "Person.h" #import "Person + Relations.h" int main (int argc, const char * argv []) @autoreleasepool Person * joe = [[Person alloc] init]; joe.name = @ "Joe"; Person * bill = [[Person alloc] init]; bill.name = @ "Bill"; Person * mary = [[Person alloc] init]; mary.name = @ "Mary"; [joe sayHello]; [joe addFriend: bill]; [joe addFriend: mary]; [joe sayHelloToFriends]; restituisce 0;
E questo è tutto ciò che c'è da creare categorie in Objective-C.
Reiterare, tutti I metodi Objective-C sono pubblici: non esiste un costrutto linguistico per contrassegnarli come privati o protetti. Invece di utilizzare metodi "veri" protetti, i programmi Objective-C possono combinare le categorie con il paradigma interfaccia / implementazione per ottenere lo stesso risultato.
L'idea è semplice: dichiarare i metodi "protetti" come categoria in un file di intestazione separato. Questo dà alle sottoclassi la possibilità di "opt-in" per i metodi protetti mentre le classi non correlate usano normalmente il file di intestazione "pubblico". Ad esempio, prendi uno standard Nave
interfaccia:
// Ship.h #import@interface Ship: NSObject - (void) shoot; @fine
Come abbiamo visto molte volte, questo definisce un metodo pubblico chiamato sparare
. Per dichiarare a protetta metodo, abbiamo bisogno di creare un Nave
categoria in un file di intestazione dedicato:
// Ship_Protected.h #import@interface Ship (Protected) - (void) prepareToShoot; @fine
Qualsiasi classe che abbia bisogno di accedere ai metodi protetti (vale a dire, la superclasse e qualsiasi sottoclasse) può semplicemente importare Ship_Protected.h
. Ad esempio, il Nave
l'implementazione dovrebbe definire un comportamento predefinito per il metodo protetto:
// Ship.m #import "Ship.h" #import "Ship_Protected.h" @implementation Ship BOOL _gunIsReady; - (void) shoot if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = SÌ; NSLog (@ "Firing!"); - (void) prepareToShoot // Esegue alcune funzionalità private. NSLog (@ "Preparazione dell'arma principale ..."); @fine
Si noti che se non avessimo importato Ship_Protected.h
, Questo prepareToShoot
l'implementazione sarebbe un metodo privato, come discusso nel Capitolo sui metodi. Senza una categoria protetta, non ci sarebbe modo per le sottoclassi di accedere a questo metodo. Facciamo la sottoclasse del Nave
per vedere come funziona. Lo chiameremo ResearchShip
:
// ResearchShip.h #import "Ship.h" @interface ResearchShip: Ship - (void) extendTelescope; @fine
Questa è un'interfaccia di sottoclasse normale, dovrebbe non importare l'intestazione protetta, in quanto renderebbe i metodi protetti disponibili a chiunque importi ResearchShip.h
, che è precisamente ciò che stiamo cercando di evitare. Infine, l'implementazione per la sottoclasse importa i metodi protetti e (facoltativamente) li sovrascrive:
// ResearchShip.m #import "ResearchShip.h" #import "Ship_Protected.h" @implementation ResearchShip - (void) extendTelescope NSLog (@ "Estendere il telescopio"); // Override protected method - (void) prepareToShoot NSLog (@ "Oh shoot! Dobbiamo trovare alcune armi!"); @fine
Per far rispettare lo stato protetto dei metodi in Ship_Protected.h
, le altre classi non sono autorizzate a importarlo. Importeranno semplicemente le normali interfacce "pubbliche" della superclasse e della sottoclasse:
// main.m #import#import "Ship.h" #import "ResearchShip.h" int main (int argc, const char * argv []) @autoreleasepool Ship * genericShip = [[Ship alloc] init]; [shoot generico]; Ship * discoveryOne = [[ResearchShip alloc] init]; [discoveryOne shoot]; restituisce 0;
Poiché nessuno dei due main.m
, Ship.h
, né ResearchShip.h
importare i metodi protetti, questo codice non avrà accesso ad essi. Prova ad aggiungere un [discoveryOne prepareToShoot]
metodo: genererà un errore del compilatore, dal momento che prepareToShoot
la dichiarazione non è stata trovata da nessuna parte.
Per riassumere, i metodi protetti possono essere emulati inserendoli in un file di intestazione dedicato e importando tale file di intestazione nei file di implementazione che richiedono l'accesso ai metodi protetti. Nessun altro file dovrebbe importare l'intestazione protetta.
Mentre il flusso di lavoro presentato qui è uno strumento organizzativo completamente valido, tieni presente che Objective-C non è mai stato concepito per supportare metodi protetti. Consideralo come un modo alternativo per strutturare un metodo Objective-C, piuttosto che un sostituto diretto dei metodi protetti in stile C # / Simula. Spesso è meglio cercare un altro modo per strutturare le classi piuttosto che forzare il codice Objective-C ad agire come un programma C #.
Uno dei maggiori problemi con le categorie è che non è possibile sovrascrivere in modo affidabile i metodi definiti nelle categorie per la stessa classe. Ad esempio, se hai definito un Aggiungi amico:
classe in Persona (Relazioni)
e più tardi decise di cambiare il Aggiungi amico:
implementazione via a Persona (Sicurezza)
categoria, non è possibile per il runtime conoscere il metodo da utilizzare poiché le categorie sono, per definizione, una struttura organizzativa piatta. Per questi tipi di casi, è necessario tornare al paradigma della sottoclasse tradizionale.
Inoltre, è importante notare che una categoria non può aggiungere variabili di istanza. Ciò significa che non è possibile dichiarare nuove proprietà in una categoria, in quanto potrebbero essere sintetizzate solo nell'implementazione principale. Inoltre, mentre una categoria ha tecnicamente accesso alle variabili di istanza delle sue classi, è meglio accedervi tramite l'interfaccia pubblica per proteggere la categoria da potenziali modifiche nel file di implementazione principale.
estensioni (chiamato anche estensioni di classe) sono un tipo speciale di categoria che richiede che i loro metodi siano definiti nel principale blocco di implementazione per la classe associata, al contrario di un'implementazione definita in una categoria. Questo può essere usato per sovrascrivere gli attributi di proprietà dichiarati pubblicamente. Ad esempio, a volte è conveniente modificare una proprietà di sola lettura in una proprietà di lettura-scrittura all'interno dell'implementazione di una classe. Considera la normale interfaccia per a Nave
classe:
Esempio di codice incluso: estensioni
// Ship.h #import#import "Person.h" @interface Ship: NSObject @property (strong, readonly) Person * capitano; - (id) initWithCaptain: (Person *) capitano; @fine
È possibile ignorare il @proprietà
definizione all'interno di un'estensione di classe. Questo ti dà l'opportunità di ri-dichiarare la proprietà come leggere scrivere
nel file di implementazione. Sintatticamente, un'estensione ha l'aspetto di una dichiarazione di categoria vuota:
// Ship.m #import "Ship.h" // L'estensione di classe. @interface Ship () @property (strong, readwrite) Persona * capitano; @end // L'implementazione standard. @implementation Ship @synthesize captain = _captain; - (id) initWithCaptain: (Person *) captain self = [super init]; if (self) // Questo funzionerà a causa dell'estensione. [Self SetCaptain: capitano]; return self; @fine
Notare la ()
aggiunto al nome della classe dopo il @interfaccia
direttiva. Questo è ciò che contrassegna come un'estensione piuttosto che un'interfaccia normale o una categoria. Qualsiasi proprietà o metodo visualizzato nell'estensione dovere essere dichiarato nel blocco di implementazione principale per la classe. In questo caso, non stiamo aggiungendo nuovi campi: stiamo sovrascrivendo uno esistente. Ma a differenza delle categorie, delle estensioni può aggiungi variabili di istanza aggiuntive a una classe, motivo per cui siamo in grado di dichiarare proprietà in un'estensione di classe ma non in una categoria.
Perché abbiamo ri-dichiarato il Capitano
proprietà con a leggere scrivere
attributo, il initWithCaptain:
metodo può usare il setCaptain:
accessoria su se stesso. Se si dovesse eliminare l'estensione, la proprietà tornerebbe al suo stato di sola lettura e il compilatore si lamenterebbe. Clienti che usano il Nave
la classe non dovrebbe importare il file di implementazione, quindi il Capitano
la proprietà rimarrà di sola lettura.
#importare#import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @autoreleasepool Person * heywood = [[Person alloc] init]; heywood.name = @ "Heywood"; Ship * discoveryOne = [[Ship alloc] initWithCaptain: heywood]; NSLog (@ "% @", [discoveryOne captain] .name); Person * dave = [[Person alloc] init]; dave.name = @ "Dave"; // Questo NON funzionerà perché la proprietà è ancora di sola lettura. [discoveryOne setCaptain: dave]; restituisce 0;
Un altro caso d'uso comune per le estensioni è per la dichiarazione di metodi privati. Nel capitolo precedente, abbiamo visto come i metodi privati possono essere dichiarati semplicemente aggiungendoli ovunque nel file di implementazione. Ma, prima di Xcode 4.3, questo non era il caso. Il modo canonico per creare un metodo privato era di inoltrarlo-dichiararlo usando un'estensione di classe. Diamo un'occhiata a questo alterando leggermente il Nave
intestazione dell'esempio precedente:
// Ship.h #import@interface Ship: NSObject - (void) shoot; @fine
Quindi, ricreamo l'esempio che abbiamo usato quando abbiamo discusso dei metodi privati in Capitolo sui metodi. Invece di aggiungere semplicemente il privato prepareToShoot
metodo per l'implementazione, dobbiamo inoltrarlo-dichiararlo in un'estensione di classe.
// Ship.m #import "Ship.h" // L'estensione di classe. @interface Ship () - (void) prepareToShoot; @ end // Il resto dell'implementazione. @implementation Ship BOOL _gunIsReady; - (void) shoot if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = SÌ; NSLog (@ "Firing!"); - (void) prepareToShoot // Esegue alcune funzionalità private. NSLog (@ "Preparazione dell'arma principale ..."); @fine
Il compilatore garantisce che i metodi di estensione siano implementati nel blocco di implementazione principale, motivo per cui funziona come una forward-declaration. Tuttavia, poiché l'estensione è incapsulata nel file di implementazione, altri oggetti non dovrebbero mai saperlo, dandoci un altro modo per emulare i metodi privati. Mentre i compilatori più recenti ti salvano questo problema, è comunque importante capire come funzionano le estensioni di classe, poiché è stato un modo comune di sfruttare i metodi privati nei programmi Objective-C fino a molto tempo fa.
Questo capitolo ha trattato due dei concetti più unici nel linguaggio di programmazione Objective-C: categorie ed estensioni. Le categorie sono un modo per estendere l'API delle classi esistenti e le estensioni sono un modo per aggiungere necessario metodi all'API al di fuori del file di interfaccia principale. Entrambi questi sono stati inizialmente progettati per facilitare l'onere di mantenere basi di codice di grandi dimensioni.
Il prossimo capitolo continua il nostro viaggio attraverso le strutture organizzative dell'Obiettivo-C. Impareremo come definire un protocollo, che è un'interfaccia che può essere implementata da una varietà di classi.
Questa lezione rappresenta un capitolo di Objective-C, un eBook gratuito del team di Syncfusion.