Categoriile sunt o funcție de limbaj Obiectiv-C care vă permite să adăugați metode noi unei clase existente, la fel ca extensiile C #. Cu toate acestea, nu confunda extensiile C # cu extensiile Objective-C. Obiectivul-C extensii sunt un caz special de categorii care vă permit să definiți metodele care trebuie să fie declarate în principalul bloc de implementare.
Acestea sunt caracteristici puternice care au multe utilizări potențiale. În primul rând, categoriile fac posibilă împărțirea unei interfețe de clasă și implementarea în mai multe fișiere, ceea ce oferă modularitatea mult mai necesară pentru proiectele mai mari. În al doilea rând, categoriile vă permit să remediați erorile într-o clasă existentă (de ex., NSString
) fără a fi nevoie să o subclasați. În al treilea rând, ele oferă o alternativă eficientă la metodele protejate și private găsite în limbile C # și în alte limbi asemănătoare Simula.
A categorie este un grup de metode asociate pentru o clasă și toate metodele definite într-o categorie sunt disponibile prin clasă ca și cum ar fi fost definite în fișierul de interfață principal. Ca exemplu, luați Persoană
cu care lucram în această carte. Dacă acesta ar fi un proiect mare, Persoană
pot avea zeci de metode variind de la comportamente de bază la interacțiuni cu alte persoane pentru verificarea identității. API-ul poate solicita ca toate aceste metode să fie disponibile printr-o singură clasă, dar este mult mai ușor pentru dezvoltatori să mențină dacă fiecare grup este stocat într-un fișier separat. În plus, categoriile elimină necesitatea de a recompila întreaga clasă de fiecare dată când modificați o singură metodă, care poate fi un economizor de timp pentru proiecte foarte mari.
Să aruncăm o privire asupra felului în care pot fi folosite categoriile pentru a realiza acest lucru. Începem cu o interfață de clasă normală și o implementare corespunzătoare:
// Person.h @interface Persoană: NSObject @interface Persoană: NSObject @property (readonly) NSMutableArray * prieteni; @property (copy) NSString * nume; - (void) sayHello; - (void) sayGoodbye; @end // Person.m #import "Person.h" @implementation Persoana @synthesize name = _name; @synthesize friends = _friends; - (id) init auto = [super init]; dacă (auto) _friends = [[NSMutableArray aloca] init]; întoarce-te; - (void) sayHello NSLog (@ "Bună ziua, spune% @", _name); - (void) sayGoodbye NSLog (@ "La revedere, spune% @", _name); @Sfârșit
Nimic nou aici - doar a Persoană
clasă cu două proprietăți ( prieteni
proprietatea va fi utilizată de categoria noastră) și două metode. Apoi, vom folosi o categorie pentru a stoca câteva metode de interacțiune cu altele Persoană
instanțe. Creați un fișier nou, dar în locul unei clase, utilizați Obiectiv-C Categorie șablon. Utilizare Relaţii pentru numele și categoria categoriei Persoană pentru Categorie pe camp:
După cum era de așteptat, acest lucru va crea două fișiere: un antet care să dețină interfața și o implementare. Cu toate acestea, acestea vor arăta puțin diferit de ceea ce am colaborat. În primul rând, să aruncăm o privire asupra interfeței:
// Person + Relații.h #import#import "Person.h" @interface Persoană (Relații) - (void) addFriend: (Person *) aFriend; - (void) removeFriend: (Persoană *) aFriend; - (void) sayHelloToFriends; @Sfârșit
În loc de normal @interface
declarație, vom include numele categoriei în paranteze după numele clasei pe care îl prelungim. Un nume de categorie poate fi orice, atâta timp cât nu intră în conflict cu alte categorii pentru aceeași clasă. Categoriile unei categorii fişier numele trebuie să fie numele clasei urmat de un semn plus, urmat de numele categoriei (de ex., Persoana + Relations.h
).
Deci, aceasta definește interfața categoriei noastre. Toate metodele pe care le adăugăm aici vor fi adăugate la original Persoană
clasă la timpul de execuție. Va apărea ca și cum ar fi adauga prieten:
, șterge prieten:
, și sayHelloToFriends
toate metodele sunt definite în Person.h
, dar ne putem păstra funcționalitatea și capacitatea de întreținere. De asemenea, rețineți că trebuie să importați antetul pentru clasa originală, Person.h
. Implementarea categoriei urmează un model similar:
// Person + Relații.m #import "Persoană + Relație.h" @implementare Persoană (Relații) - (void) addFriend: (Persoană *) aFriend [[prieteni de sine] addObject: aFriend]; - (void) removeFriend: (Persoana *) aFriend [[prieteni sine] removeObject: aFriend]; - (void) sayHelloToFriends pentru (Persoană * prietenă în [prieteni singuri]) NSLog (@ "Bună ziua,% @!", [nume prieten]); @Sfârșit
Aceasta implementează toate metodele din Persoana + Relations.h
. La fel ca interfața categoriei, numele categoriei apare în paranteze după numele clasei. Numele categoriei în implementare ar trebui să se potrivească cu cel din interfață.
De asemenea, rețineți că nu există nicio modalitate de a defini proprietăți suplimentare sau variabile de instanță într-o categorie. Categoriile trebuie să se refere la datele stocate în clasa principală (prieteni
in aceasta instanta).
De asemenea, este posibil să se ignore implementarea din Person.m
prin simpla redefinire a metodei în Persoana + Relations.m
. Acest lucru poate fi folosit pentru a patch-uri de maimuță o clasă existentă; cu toate acestea, nu este recomandat dacă aveți o soluție alternativă la problemă, deoarece nu ar exista nicio modalitate de a suprascrie implementarea definită de categorie. Adică, spre deosebire de ierarhia de clasă, categoriile sunt o structură organizațională plană - dacă implementați aceeași metodă în două categorii separate, este imposibil ca timpul de execuție să dau seama care dintre acestea să fie utilizat.
Singura modificare pe care trebuie să o faceți pentru a utiliza o categorie este să importați fișierul de antet al categoriei. După cum puteți vedea în exemplul următor, Persoană
clasa are acces la metodele definite în Person.h
împreună cu cele definite în categorie Persoana + Relations.h
:
// main.m #import#import "Person.h" #import "Persoană + Relație.h" int principală (int argc, const char * argv []) @autoreleasepool Person * joe = [[Person alloc] init]; joe.name = @ "Joe"; Persoană * factură = [[Person alloc] init]; bill.name = @ "Bill"; Persoana * mary = [[Person alloc] init]; mary.name = @ "Mary"; [joe sayHello]; [Joe addFriend: Bill]; [joe addFriend: mary]; [joe sayHelloToFriends]; retur 0;
Și asta e totul pentru a crea categorii în Obiectiv-C.
A reitera, toate Obiectivele C sunt publice - nu există construcție de limbi care să le marcheze ca fiind private sau protejate. În loc să folosească metode "adevărate" protejate, programele Objective-C pot combina categorii cu paradigma interfeței / implementării pentru a obține același rezultat.
Ideea este simplă: declararea metodelor "protejate" drept o categorie într-un fișier antet separat. Acest lucru oferă subclaselor abilitatea de a "opta" la metodele protejate, în timp ce clasele care nu au legătură folosesc fișierul antet "public" ca de obicei. De exemplu, luați un standard Navă
interfaţă:
// Ship.h #import@interface Ship: NSObject - (void) trage; @Sfârșit
Așa cum am văzut de mai multe ori, aceasta definește o metodă publică numită trage
. Pentru a declara a protejat metoda, trebuie să creați o Navă
categorie într-un fișier antet dedicat:
// Ship_Protected.h #import@interface Ship (protejat) - (void) prepareToShoot; @Sfârșit
Orice clase care au nevoie de acces la metode protejate (și anume, superclase și orice subclase) pot pur și simplu să importe Ship_Protected.h
. De exemplu, Navă
implementarea ar trebui să definească un comportament implicit pentru metoda protejată:
// Ship.m #import "Ship.h" #import "Ship_Protected.h" @implementarea navei BOOL _gunIsReady; - (void) trage if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = DA; NSLog (@ "Firing!"); - (void) prepareToShoot // Executați unele funcționalități private. NSLog (@ "Pregătirea armei principale ..."); @Sfârșit
Rețineți că dacă nu am fi importat Ship_Protected.h
, acest prepareToShoot
punerea în aplicare ar fi o metodă privată, așa cum este discutată în Capitolul de metode. Fără o categorie protejată, subclasele nu ar fi accesibile pentru această metodă. Să subclasăm Navă
pentru a vedea cum funcționează aceasta. O să-l sunăm ResearchShip
:
// ResearchShip.h #import "Ship.h" @interface ResearchShip: navă - (void) extendTelescope; @Sfârșit
Aceasta este o interfață normală subclasă - ar trebui nu importați antetul protejat, deoarece acest lucru ar face ca metodele protejate să fie disponibile pentru oricine importă ResearchShip.h
, care este exact ceea ce încercăm să evităm. În cele din urmă, implementarea pentru subclasa importă metodele protejate și (opțional) le suprapune:
// ResearchShip.m #import "ResearchShip.h" #import "Ship_Protected.h" @implementation CercetareShip - (void) extendTelescope NSLog (@ "Extinderea telescopului"); // Override metoda protejata - (void) prepareToShoot NSLog (@ "Oh trage! Trebuie sa gasim cateva arme!"); @Sfârșit
Pentru a impune statutul protejat al metodelor în Ship_Protected.h
, alte clase nu au voie să le importe. Vor importa interfețele normale "publice" ale superclasei și subclaselor:
// main.m #import#import "Ship.h" #import "ResearchShip.h" int principal (int argc, const char * argv []) @autoreleasepool navă * genericShip = [[alocare navă] init]; [trage genericShip]; Nava * descoperireaOne = [[ResearchShip alloc] init]; [detectareOne trage]; retur 0;
Deoarece nici unul main.m
, Ship.h
, nici ResearchShip.h
importați metodele protejate, acest cod nu va avea acces la ele. Încercați să adăugați o [discoveryOne prepareToShoot]
metoda - va arunca o eroare de compilator, deoarece prepareToShoot
declarația nu este de găsit.
Pentru a rezuma, metode protejate pot fi emulate prin plasarea lor într-un fișier antet dedicat și importarea acelui fișier antet în fișierele de implementare care necesită acces la metodele protejate. Niciun alt fișier nu ar trebui să importe antetul protejat.
În timp ce fluxul de lucru prezentat aici este un instrument organizațional complet valabil, rețineți că obiectivul C nu a fost niciodată menit să susțină metode protejate. Gândiți-vă la acest lucru ca pe un mod alternativ de a structura o metodă Obiectiv-C, mai degrabă decât o înlocuire directă a metodelor protejate în stil C # / Simula. Este adesea mai bine să căutați un alt mod de a structura cursurile, mai degrabă decât să vă forțați codul Obiectiv-C să acționați ca un program C #.
Una dintre cele mai mari probleme cu categorii este că nu puteți ignora în mod fiabil metodele definite în categorii pentru aceeași clasă. De exemplu, dacă ați definit un adauga prieten:
clasă în Persoana (relații)
și mai târziu a decis să schimbe adauga prieten:
implementarea prin Persoana (Securitate)
categorie, nu există nici o modalitate de a cunoaște metoda de rulare, deoarece categoriile sunt, prin definiție, o structură organizatorică plat. Pentru astfel de cazuri, trebuie să reveniți la paradigma tradițională de subclasificare.
De asemenea, este important să rețineți că o categorie nu poate adăuga variabile de instanță. Aceasta înseamnă că nu puteți declara proprietăți noi într-o categorie, deoarece acestea ar putea fi sintetizate numai în implementarea principală. În plus, în timp ce o categorie tehnic are acces la variabilele de instanțe ale clasei sale, este o practică mai bună să le accesați prin interfața lor publică pentru a proteja categoria de eventualele modificări din fișierul principal de implementare.
Extensii (numit si Extensiile de clasă) sunt un tip special de categorie care necesită definirea metodelor lor în principal bloc de implementare pentru clasa asociată, spre deosebire de o implementare definită într-o categorie. Aceasta poate fi utilizată pentru a suprascrie atributele de proprietăți declarate public. De exemplu, este uneori convenabil să modificați o proprietate read-only într-o proprietate read-write în cadrul unei aplicații de clasă. Luați în considerare interfața normală pentru a Navă
clasă:
Eșantion de cod inclus: Extensii
// Ship.h #import#import "Person.h" @interface Ship: NSObject @property (puternic, readonly) Persoană * căpitan; - (id) initWithCaptain: (persoană *) căpitan; @Sfârșit
Este posibil să ignorați @proprietate
definiție în cadrul unei extensii de clasă. Acest lucru vă oferă posibilitatea de a re-declara proprietatea ca fiind Citeste, scrie
în fișierul de implementare. În mod sintactic, o extensie arată ca o declarație de categorie goală:
// Ship.m #import "Ship.h" // Extensia de clasă. @interface Ship () @property (strong, readwrite) Persoana * căpitan; @end // Implementarea standard. @implementation Ship @synthesize captain = _captain; - (id) initWithCaptain: (persoană *) căpitan self = [super init]; dacă (self) // Aceasta va funcționa din cauza extensiei. [auto setCaptain: capitan]; întoarce-te; @Sfârșit
Rețineți ()
adăugat la numele clasei după @interface
directivă. Aceasta este ceea ce o marchează ca o extensie, mai degrabă decât o interfață normală sau o categorie. Toate proprietățile sau metodele care apar în extensie trebuie sa să fie declarată în blocul principal de implementare pentru clasă. În acest caz, nu adăugăm noi câmpuri - depășim unul existent. Dar spre deosebire de categorii, extensii poate sa adăugați variabile de instanță suplimentare la o clasă, de aceea suntem capabili să declare proprietăți într-o extensie de clasă, dar nu într-o categorie.
Pentru că am re-declarat căpitan
proprietate cu un Citeste, scrie
atribut, initWithCaptain:
metoda poate folosi setCaptain:
accesor în sine. Dacă ați șterge extensia, proprietatea ar reveni la starea sa numai pentru citire și compilatorul s-ar plânge. Clienții care utilizează Navă
clasa nu ar trebui să importe fișierul de implementare, așa că căpitan
proprietatea va rămâne doar pentru citire.
#import#import "Person.h" #import "Ship.h" int principal (int argc, const char * argv []) @autoreleasepool Persoana * heywood = [[Person aloca] init]; heywood.name = @ "Heywood"; Nava * descoperireaOne = [[Alocarea navei] initWithCaptain: heywood]; NSLog (@ "% @", [descoperirea unui căpitan] .name); Persoana * dave = [[Person alloc] init]; dave.name = @ "Dave"; // Aceasta nu va funcționa deoarece proprietatea este încă numai pentru citire. [discoveryOne setCaptain: dave]; retur 0;
Un alt caz de utilizare obișnuit pentru extensii este declanșarea metodelor private. În capitolul anterior, am văzut cum pot fi declarate metode private prin adăugarea lor oriunde în fișierul de implementare. Dar, înainte de Xcode 4.3, nu a fost cazul. Metoda canonică de a crea o metodă privată a fost aceea de a transmite-o cu o extensie de clasă. Să aruncăm o privire la acest lucru prin modificarea ușor Navă
antet din exemplul anterior:
// Ship.h #import@interface Ship: NSObject - (void) trage; @Sfârșit
Apoi, vom recrea exemplul pe care l-am folosit când am discutat despre metodele private în Capitolul de metode. În loc de a adăuga pur și simplu privat prepareToShoot
metoda de punere în aplicare, trebuie să-l transmitem într-o extensie de clasă.
// Ship.m #import "Ship.h" // Extensia de clasă. @interface Ship () - (void) prepareToShoot; @end // Restul implementării. @implementation Ship BOOL _gunIsReady; - (void) trage if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = DA; NSLog (@ "Firing!"); - (void) prepareToShoot // Executați unele funcționalități private. NSLog (@ "Pregătirea armei principale ..."); @Sfârșit
Compilatorul asigură că metodele de extensie sunt implementate în blocul principal de implementare, motiv pentru care funcționează ca o declarație în avans. Cu toate acestea, deoarece extensia este încapsulată în fișierul de implementare, alte obiecte nu ar trebui să știe vreodată despre acest lucru, oferindu-ne un alt mod de a emula metode private. În timp ce compilatorii mai noi vă salvează aceste probleme, este important să înțelegeți cum funcționează extensiile de clasă, deoarece a fost un mod obișnuit de a mobiliza metode private în programele Obiectiv-C până la foarte recent.
Acest capitol a acoperit două dintre conceptele mai unice din limbajul de programare Obiectiv-C: categorii și extensii. Categoriile reprezintă o modalitate de a extinde API-ul claselor existente, iar extensiile reprezintă o modalitate de adăugare necesar metode pentru API în afara fișierului principal de interfață. Ambele au fost inițial concepute pentru a ușura povara menținerii unor baze de coduri mari.
Următorul capitol continuă călătoria noastră prin structurile organizaționale ale obiectivului C. Vom învăța cum să definim un protocol, care este o interfață care poate fi implementată de o varietate de clase.
Această lecție reprezintă un capitol din Obiectiv-C Succinct, o carte electronică gratuită de la echipa de la Syncfusion.