În acest capitol, vom explora metodele Obiectiv-C cu mult mai multe detalii decât în capitolele anterioare. Aceasta include o discuție aprofundată a metodelor de instanță, a metodelor de clasă, a metodelor importante construite, a moștenirii, a convențiilor de denumire și a modelelor comune de design.
Am lucrat cu metodele de instanță și de clasă în această carte, dar să luăm un moment pentru a formaliza cele două categorii majore de metode din Obiectiv-C:
După cum am văzut de mai multe ori, metodele instanțelor sunt notate cu o cratimă înainte de numele metodei, în timp ce metodele de clasă sunt prefixate cu un semn plus. De exemplu, să luăm o versiune simplificată a noastră Person.h
fişier:
@interface Persoana: NSObject @property (copy) NSString * name; - (void) sayHello; + (Persoană *) persoanăWithName: (NSString *) nume; @Sfârșit
De asemenea, metodele de implementare corespunzătoare trebuie să fie precedate de o cratimă sau de un semn plus. Deci, un minim Person.m
ar putea să arate ceva asemănător:
#import "Person.h" @implementation Persoană @synthesize name = _name; - (void) spune Hello NSLog (@ "HELLO"); + (Persoană *) personWithName: (NSString *) numele Person * person = [[Person alloc] init]; person.name = nume; persoana returnata; @Sfârșit
spune buna
metoda poate fi numită de către instanțe din Persoană
clasa, în timp ce personWithName
metoda poate fi numită doar de clasa însăși:
Persoană * p1 = [Persoană persoană cu nume: @ "Frank"]; // Metoda de clasă. [p1 sayHello]; // Metoda instanței.
Cele mai multe dintre acestea ar trebui să vă fie cunoscute până acum, dar acum avem ocazia să vorbim despre unele dintre convențiile unice din obiectivul C.
În orice mediu orientat pe obiecte, este important să puteți accesa metode din clasa părinte. Obiectivul C utilizează o schemă foarte asemănătoare cu cea a lui C #, cu excepția locului baza
, utilizează super
cuvinte cheie. De exemplu, următoarea punere în aplicare a spune buna
ar afișa SALUT
în panoul de ieșire și apoi apelați versiunea de clasă părinte " spune buna
:
- (void) spune Hello NSLog (@ "HELLO"); [super sayHello];
Spre deosebire de C #, metodele de înlocuire nu trebuie să fie marcate în mod explicit ca atare. Veți vedea acest lucru cu ambele init
și dealloc
metode discutate în secțiunea următoare. Chiar dacă acestea sunt definite pe NSObject
clasa, compilatorul nu se plânge când creați propria dvs. init
și dealloc
metodelor din subclasele.
Metodele de inițializare sunt necesare pentru toate obiectele - un obiect nou alocat nu este considerat "gata de utilizat" până când nu a fost apelată una dintre metodele sale de inițializare. Acestea sunt locul pentru a seta valorile implicite pentru variabilele de exemplu și pentru a configura altfel starea obiectului. NSObject
clasa definește o valoare implicită init
metodă care nu face nimic, dar este adesea util să vă creați propriul. De exemplu, un obicei init
punerea în aplicare pentru noi Navă
clasa ar putea atribui valori implicite unei variabile de instanță numite _ammo
:
- (id) init auto = [super init]; dacă (auto) _ammo = 1000; întoarce-te;
Aceasta este modalitatea canonică de a defini un obicei init
metodă. de sine
cuvântul cheie este echivalentul lui C # acest
-este folosit pentru a se referi la instanța care apelează metoda, ceea ce face posibil ca un obiect să trimită mesaje către el însuși. După cum puteți vedea, totul init
sunt necesare metode pentru a returna instanța. Aceasta este ceea ce face posibilă utilizarea [[Alocare navă]]
sintaxă pentru a atribui instanța unei variabile. De asemenea, observați că, deoarece NSObject
interfața declară init
metoda, nu este nevoie să adăugați un init
declarație către Ship.h
.
În timp ce simplu init
metodele precum cea prezentată în proba anterioară sunt utile pentru setarea valorii implicite a variabilelor instanței, adesea este mai convenabil să se transmită parametrii unei metode de inițializare:
- (id) initWithAmmo: (nesemnate int) theAmmo auto = [super init]; dacă (auto) _ammo = theAmmo; întoarce-te;
Dacă veniți dintr-un fundal C #, s-ar putea să nu vă simțiți bine initWithAmmo
numele metodei. Probabil vă așteptați să vedeți muniții
parametru separat de numele metodei reale cum ar fi void init (muniție uint)
; totuși, denumirea metodei Obiectiv-C se bazează pe o filosofie cu totul diferită.
Amintiți-vă că obiectivul obiectivului C este de a forța un API să fie cât mai descriptiv posibil, asigurându-se că nu există absolut nicio confuzie cu privire la ceea ce va face un apel de metodă. Nu vă puteți gândi la o metodă ca o entitate separată de parametrii săi - sunt o singură unitate. Această decizie de proiectare se reflectă de fapt în implementarea obiectivului C, care nu face nicio distincție între o metodă și parametrii ei. Pe plan intern, numele metodei este de fapt lista de parametri concatenate.
De exemplu, luați în considerare următoarele trei declarații de metodă. Rețineți că a doua și a treia nu sunt metode integrate NSObject
, deci tu do trebuie să le adăugați la interfața de clasă înainte de a le implementa.
- (Id) init; - (id) initWithAmmo: (nesemnate int) theAmmo; - (id) initWithAmmo: (unsigned int) căpitanul lui Ammo: (Persoana *) Captain;
În timp ce acest lucru arată ca supraîncărcarea metodei, nu este tehnic. Acestea nu sunt variații ale init
metoda - toate sunt metode complet independente cu nume distincte de metode. Numele acestor metode sunt după cum urmează:
init initWithAmmo: initWithAmmo: căpitan:
Acesta este motivul pentru care vedeți notația de genul indexOfObjectWithOptions: passingTest:
și indexOfObjectAtIndexes: opțiuni: passingTest:
pentru referirea la metodele din documentația oficială Obiectiv-C (preluată din NSArray).
Din punct de vedere practic, aceasta înseamnă că primul parametru al metodelor dvs. ar trebui să fie întotdeauna descris de numele metodei "primare". Metode ambigue precum cele de mai jos sunt, în general, încruntate de către programatorii Obiectiv-C:
- (id) trage: (nava *) aShip;
În schimb, ar trebui să utilizați o preposition pentru a include primul parametru în numele metodei, după cum urmează:
- (id) shootOtherShip: (navă *) aShip;
Inclusiv ambele OtherShip
și o nava
în definiția metodei poate părea redundantă, dar nu uitați că o nava
argumentul este utilizat numai pe plan intern. Cineva care cheamă metoda va scrie ceva de genul shootOtherShip: discoveryOne
, Unde discoveryOne
este variabila care conține nava pe care doriți să o trageți. Acesta este exact genul de verbozitate pe care dezvoltatorii obiectiv-C se străduiesc.
În plus față de init
metodă de inițializare instanțe, Obiectivul C oferă, de asemenea, o modalitate de a înființa clase. Înainte de a apela orice metodă de clasă sau de a instanți orice obiecte, timpul de executare a obiectivului-C apelează inițializa
clasa de clasă în cauză. Aceasta vă oferă posibilitatea de a defini orice variabile statice înainte ca cineva să utilizeze clasa. Una dintre cele mai comune cazuri de utilizare pentru acest lucru este de a crea singletons:
nave statice * _sharedShip; + (void) inițializează if (self == [Class ship]) _sharedShip = [[self alloc] init]; + (Navă *) sharedShip return _sharedShip;
Înainte de prima dată [ShipShipShip]
se numește, timpul de rulare va apela [Initializeaza nava]
, ceea ce face ca singleton-ul să fie definit. Modificatorul variabil static servește aceluiași scop ca în C # -i creează o variabilă de nivel de clasă în locul unei variabile de instanță. inițializa
metoda se numește doar o singură dată, dar se numește în toate clasele super, deci trebuie să aveți grijă să nu inițializați variabilele de nivel de clasă de mai multe ori. De aceea am inclus auto == [Clasa navei]
condiționată să se asigure _shareShip
este alocată numai în Navă
clasă.
De asemenea, rețineți că în interiorul unei metode de clasă, de sine
cuvântul cheie se referă la clasa însăși, nu la o instanță. Asa de, [auto-alocare]
în ultimul exemplu este echivalentul lui [Alocarea navei]
.
Contrapartida logică a metodei de inițializare a instanței este dealloc
metodă. Această metodă este apelată pe un obiect atunci când numărul său de referință ajunge la zero și memoria sa de bază este pe cale să fie dezalocată.
Dacă utilizați gestionarea manuală a memoriei (nu este recomandată), trebuie să eliberați orice variabile de instanță pe care le-ați alocat obiectul în dealloc
metodă. Dacă nu eliberezi variabile de instanță înainte ca obiectul să iasă din domeniul de aplicare, veți avea indicatori care leagă variabilele de instanță, ceea ce înseamnă că au scurs memoria ori de câte ori este lansată o instanță a clasei. De exemplu, dacă noi Navă
clasa a alocat o variabilă numită _gun
în init
ar trebui să o eliberați dealloc
. Acest lucru este demonstrat în următorul exemplu (Gun.h
conține o interfață goală care definește pur și simplu armă
clasă):
#import "Ship.h" #import "Gun.h" @implementarea navei BOOL _gunIsReady; Arma * _gun; - (id) init auto = [super init]; dacă (auto) _gun = [[Gun alloc] init]; întoarce-te; - (void) dealloc NSLog (@ "Dealocarea unei nave"); [eliberarea _gun]; [super dealloc]; @Sfârșit
Poti vedea dealloc
metoda în acțiune prin crearea unui Navă
și eliberând-o, după cum urmează:
int principal (int argc, const char * argv []) @autoreleasepool nava * nava = [[alocare nava] init]; [autorelease de navă]; NSLog (@ "Nava ar trebui să mai existe în autoreleasepool"); NSLog (@ "Navele ar trebui să fie alocate deocamdată"); retur 0;
Acest lucru demonstrează, de asemenea, modul în care funcționează obiectele eliberate automat. dealloc
metoda nu va fi chemată până la sfârșitul anului @autoreleasepool
bloc, astfel încât codul anterior ar trebui să emită următoarele:
Nava ar trebui să mai existe în autoreleasepool Dealocarea unei nave de navă ar trebui să fie dealocată până acum
Rețineți că primul NSLog ()
mesaj în principal()
este afisat inainte de cel din dealloc
metoda, chiar dacă a fost numită după AutoreleasePool
apel.
Cu toate acestea, dacă utilizați numărarea automată a referințelor, toate variabilele de instanță vor fi automat dealocate și [super dealloc]
va fi chemat și pentru voi (nu ar trebui să-l numiți în mod explicit). Deci, singurul lucru pe care trebuie să vă faceți griji sunt variabilele non-obiect, cum ar fi bufferele create cu C malloc ()
.
Ca init
, nu trebuie să implementați a dealloc
dacă obiectul dvs. nu are nevoie de manipulare specială înainte de a fi eliberat. Acest lucru este adesea cazul pentru mediile automate de numărare a referințelor.
Un obstacol important pentru dezvoltatorii C # care trec la obiectivul C este lipsa aparentă a metodelor private. Spre deosebire de C #, toate metodele dintr-o clasă obiectiv-C sunt accesibile terților; cu toate acestea, este posibil să emula comportamentul metodelor private.
Amintiți-vă că clienții importează doar interfața unei clase (adică fișierele cu antet) - acestea nu ar trebui să vadă implementarea de bază. Deci, prin adăugarea de noi metode în interiorul punerea în aplicare fișier fără a le include în interfață, putem ascunde eficient metode de la alte obiecte. Cu toate acestea, aceasta este mai mult bazată pe convenții decât metodele "adevărate" private, dar este în esență aceeași funcție: încercarea de a apela o metodă care nu este declarată într-o interfață va duce la o eroare de compilator.
Încercarea de a apela o metodă "privată"De exemplu, să presupunem că trebuie să adăugați o imagine privată prepareToShoot
metoda pentru a Navă
clasă. Tot ce trebuie să faceți este să omiteți Ship.h
adăugând în același timp Ship.m
:
// Ship.h @ nava de interfață: NSObject @property (slab) Person * captain; - (void) trage; @Sfârșit
Aceasta declară o metodă publică numită trage
, care va folosi serviciul privat prepareToShoot
metodă. Implementarea corespunzătoare ar putea să arate cam ca:
// Ship.m #import "Ship.h" @implementarea navei BOOL _gunIsReady; @synthesize captain = _captain; - (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
Începând cu Xcode 4.3, puteți defini metode private oriunde în implementare. Dacă utilizați metoda privată înainte ca compilatorul să fi văzut-o (ca în exemplul anterior), compilatorul verifică restul blocului de implementare pentru definiția metodei. Înainte de Xcode 4.3, a trebuit fie să definiți o metodă privată inainte de a fost folosită în altă parte a dosarului, sau a declarat-o cu un mesaj extensie de clasă.
Extensiile de clasă reprezintă un caz special categorii, care sunt prezentate în capitolul următor. Așa cum nu există nicio modalitate de a marca o metodă privată, nu există nicio modalitate de a marca o metodă ca fiind protejată; totuși, după cum vom vedea în capitolul următor, categoriile oferă o alternativă puternică la metodele protejate.
Selectorii sunt modalitatea de a reprezenta metodele obiectivului C. Ele vă permit să selectați dinamic una dintre metodele unui obiect, care pot fi folosite pentru a se referi la o metodă la timpul de execuție, pentru a transmite o metodă unei alte funcții și a afla dacă un obiect are o anumită metodă. În scopuri practice, vă puteți gândi la un selector ca nume alternativ pentru o metodă.
Dezvoltarea reprezentării unei metode vs. reprezentarea obiectivului CPe plan intern, obiectivul C utilizează un număr unic pentru a identifica fiecare nume de metodă folosit de program. De exemplu, o metodă numită spune buna
s-ar putea traduce 4984331082
. Acest identificator este numit a selector, și este o modalitate mult mai eficientă pentru compilator să se refere la metode decât reprezentarea completă a șir lor. Este important să înțelegeți că un selector reprezintă doar metoda Nume-nu o implementare specifică a metodei. Cu alte cuvinte, a spune buna
metoda definită de Persoană
clasa are același selector ca a spune buna
metoda definită de Navă
clasă.
Cele trei instrumente principale pentru a lucra cu selectori sunt:
@selector ()
- Reveniți selectorul asociat cu un nume de cod sursă.NSSelectorFromString ()
- Reveniți selectorul asociat cu reprezentarea șirului unui nume de metodă. Această funcție permite definirea numelui metodei la timpul de execuție, dar este mai puțin eficientă decât @selector ()
.NSStringFromSelector ()
- Returnați reprezentarea șirului unui nume de metodă dintr-un selector.După cum puteți vedea, există trei modalități de a reprezenta un nume de metodă în Obiectiv-C: ca sursă, ca șir sau ca selector. Aceste funcții de conversie sunt prezentate grafic în figura următoare:
Se convertesc între codul sursă, șirurile și selectoriiSelectorii sunt stocați într-un tip de date special numit SEL
. Fragmentul următor demonstrează utilizarea de bază a celor trei funcții de conversie prezentate în figura precedentă:
int principal (int argc, const char * argv []) @autoreleasepool SEL selector = @selector (sayHello); NSLog (@ "% @", NSStringFromSelector (selector)); dacă (selector == NSSelectorFromString (@ "sayHello")) NSLog (@ "Selectorii sunt egali!"); return 0;
În primul rând, folosim @selector ()
directivă pentru a descoperi selectorul pentru o metodă numită spune buna
, care reprezintă o reprezentare a codului sursă a unui nume de metodă. Rețineți că puteți trece orice numele metodei @selector ()
-nu trebuie să existe în altă parte în programul tău. Apoi, folosim NSStringFromSelector ()
pentru a converti selectorul înapoi într-un șir, astfel încât să îl putem afișa în panoul de ieșire. În cele din urmă, condiționarea arată că selectorii au o corespondență unu-la-unu cu numele metodelor, indiferent dacă le găsiți prin nume de metode codate greu sau șiruri de caractere.
Exemplul precedent utilizează o metodă simplă care nu ia parametri, dar este important să puteți trece în jurul metodelor do accepta parametrii. Amintiți-vă că numele unei metode constă în numele metodei primare concatenate cu toate numele parametrilor. De exemplu, o metodă cu semnătură
- (void) sayHelloToPerson: (persoană *) aPerson cuGreeting: (NSString *) aGreeting;
ar avea o metodă Nume de:
sayHelloToPerson: withGreeting:
La asta treci @selector ()
sau NSSelectorFromString ()
pentru a returna identificatorul acelei metode. Selectorii lucrează numai cu metoda numele (nu semnături), așa că nu există nu o corespondență unu-la-unu între selectori și semnături. Ca rezultat, metoda Nume în ultimul exemplu se va potrivi, de asemenea, o semnătură cu diferite tipuri de date, inclusiv următoarele:
- (void) sayHelloToPerson: (NSString *) a Numele cuGreeting: (BOOL) useGreeting;
Verbositatea convențiilor de denumire ale obiectivului C evită cele mai confuze situații; cu toate acestea, selectorii pentru metodele cu un parametru pot fi în continuare dificil, deoarece adăugarea unui colon la numele metodei o schimbă de fapt în a complet diferit metodă. De exemplu, în următorul exemplu, primul nume de metodă nu ia un parametru, în timp ce cel de-al doilea face:
Hai spune Hello:
Din nou, convențiile de numire merg departe spre eliminarea confuziei, dar trebuie să vă asigurați că știți când este necesar să adăugați un colon la sfârșitul unui nume de metodă. Aceasta este o problemă obișnuită dacă sunteți nou la selectori și poate fi greu de depanat, deoarece un colon care trail încă creează un nume de metode perfect valabil.
Desigur, înregistrarea unui selector într-o SEL
variabila este relativ inutilă fără capacitatea de ao executa ulterior. Deoarece un selector este doar o metodă Nume (nu o implementare), trebuie întotdeauna asociată cu un obiect înainte de ao putea apela. NSObject
clasa definește a performSelector:
pentru acest scop.
[joe performSelector: @selector (sayHello)];
Acesta este echivalentul apelului spune buna
direct pe joe
:
[joe sayHello];
Pentru metodele cu unul sau doi parametri, puteți utiliza cele asociate performSelector: withObject:
și performSelector: withObject: withObject:
metode. Următoarea implementare a metodei:
- (void) sayHelloToPerson: (Persoană *) aPerson NSLog (@ "Bună ziua,% @", [nume de utilizator]);
ar putea fi numit dinamic prin trecerea o persoana
argument pentru a performSelector: withObject:
, așa cum sa demonstrat aici:
[joe performSelector: @selector (sayHelloToPerson :) cu Object: Bill];
Acesta este echivalentul trecerii parametrului direct la metodă:
[joe sayHelloToPerson: proiect de lege];
De asemenea, performSelector: withObject: withObject:
vă permite să treceți doi parametri la metoda țintă. Singura avertizare cu acestea este că toți parametrii și valoarea returnată a metodei trebuie să fie obiecte - nu funcționează cu tipuri de date primitive C int
, pluti
, etc Dacă aveți nevoie de această funcționalitate, puteți fie caseta de tip primitiv într-una dintre cele mai multe Obiectiv-C clase de wrapper (de ex.., NSNumber
) sau utilizați obiectul NSInvocation pentru a încapsula un apel complet al metodei.
Nu este posibilă efectuarea unui selector pe un obiect care nu a definit metoda asociată. Dar, spre deosebire de apelurile metodelor statice, nu este posibil să se determine la momentul compilării dacă performSelector:
va ridica o eroare. În schimb, trebuie să verificați dacă un obiect poate răspunde unui selector la momentul executării, folosind numele corespunzător respondsToSelector:
metodă. Pur și simplu se întoarce DA
sau NU
în funcție de faptul dacă obiectul poate efectua selectorul:
SEL metodaToCall = @selector (sayHello); dacă ([joe răspundeToSelector: methodToCall]) [joe performSelector: methodToCall]; altceva NSLog (@ "Joe nu știe cum să efectueze% @", NSStringFromSelector (methodToCall));
Dacă selectorii dvs. sunt generați dinamic (de exemplu, dacă methodToCall
este selectată dintr-o listă de opțiuni) sau nu aveți control asupra obiectului vizat (de ex., joe
poate fi unul dintre mai multe tipuri diferite de obiecte), este important să executați această verificare înainte de a încerca să apelați performSelector:
.
Întreaga idee din spatele selectorilor este aceea de a putea să treci prin metode la fel cum treci în jurul obiectelor. Acest lucru poate fi folosit, de exemplu, pentru a defini dinamic o "acțiune" pentru a Persoană
obiect pentru a fi executat ulterior în program. De exemplu, luați în considerare următoarea interfață:
Eșantion de cod inclus: selectori
@interface Persoana: NSObject @property (copy) NSString * name; @property (slab) Person * prieten; acțiune @ SELPER; - (void) sayHello; - (void) sayGoodbye; - (void) coerceFriend; @Sfârșit
Împreună cu implementarea corespunzătoare:
#import "Person.h" @implementation Persoană @synthesize name = _name; @synthesize friend = _friend; @synthesize action = _action; - (void) sayHello NSLog (@ "Bună ziua, spune% @", _name); - (void) sayGoodbye NSLog (@ "La revedere, spune% @", _name); - (void) coerceFriend NSLog (@ "% @ este pe cale să facă% @ face ceva", _name, [nume prieten]); [_friend performSelector: _action]; @Sfârșit
După cum puteți vedea, apelarea coerceFriend
metoda va forța a diferit obiect pentru a efectua unele acțiuni arbitrare. Acest lucru vă permite să configurați o prietenie și un comportament timpuriu în program și să așteptați să apară un anumit eveniment înainte de declanșarea acțiunii:
#import#import "Person.h" NSString * askUserForAction () // În lumea reală, aceasta ar fi capturarea unor // informații introduse de utilizator pentru a determina ce metodă să apelați. NSString * theMethod = @ "sayGoodbye"; returnațiMetoda; int main (int argc, const char * argv []) @autoreleasepool // Creați o persoană și determinați o acțiune de efectuat. Persoana * joe = [[Person alloc] init]; joe.name = @ "Joe"; Persoană * factură = [[Person alloc] init]; bill.name = @ "Bill"; joe.friend = factură; joe.action = NSSelectorFromString (askUserForAction ()); // Așteptați un eveniment ... // Efectuați acțiunea. [joe coerceFriend]; retur 0;
Aceasta este aproape exact modul în care sunt implementate componentele interfeței utilizator în iOS. De exemplu, dacă ați avea un buton, îl configurați cu un obiect țintă (de ex., prieten
) și o acțiune (de ex., acțiune
). Apoi, când utilizatorul apasă în cele din urmă butonul, poate folosi performSelector:
pentru a executa metoda dorită pe obiectul corespunzător. Permiterea obiectului și metoda de a varia independent oferă o flexibilitate semnificativă - butonul ar putea să efectueze literalmente orice acțiune cu orice obiect, fără a schimba în nici un fel clasa butonului. Aceasta constituie, de asemenea, baza modelului de proiectare țintă-acțiune, care se bazează în mare măsură pe iOS Succinct carte de însoțitoare.
În acest capitol, am abordat metodele de instanță și de clasă, împreună cu unele dintre cele mai importante metode integrate. Am lucrat îndeaproape cu selectorii, care sunt o modalitate de a se referi la numele de metode fie ca sursă, fie ca șiruri de caractere. De asemenea, am examinat pe scurt modelul de proiectare Target-Action, care este un aspect integral al programării iOS și OS X..
Următorul capitol discută o modalitate alternativă de a crea metode private și protejate în Obiectiv-C.
Această lecție reprezintă un capitol din Obiectiv-C Succinct, o carte electronică gratuită de la echipa de la Syncfusion.