Obiectiv-C Succinct Managementul memoriei

Memoria trebuie să fie alocată pentru fiecare obiect pe care îl folosește aplicația dvs. și trebuie să fie dealocat atunci când cererea este făcută cu ea pentru a vă asigura că aplicația dvs. folosește memoria cât mai eficient posibil. Este important să înțelegeți mediul de gestionare a memoriei obiectivului C, pentru a vă asigura că programul dvs. nu scapă de memorie sau că încercați să faceți referire la obiecte care nu mai există.

Numărarea referințelor la un obiect

Spre deosebire de C #, Obiectiv-C nu nu utilizați colectarea deșeurilor. În schimb, utilizează un mediu de numărare a referințelor care urmărește numărul de locuri care utilizează un obiect. Atâta timp cât există cel puțin o referință la obiect, timpul de execuție Object-C asigură că obiectul va locui în memorie. Totuși, dacă nu mai există referințe la obiect, runtime-ul este permis să elibereze obiectul și să utilizeze memoria pentru altceva. Dacă încercați să accesați un obiect după ce acesta a fost lansat, este posibil ca programul dvs. să se prăbușească cel mai probabil.

Există două modalități exclusive de a gestiona referințele obiectului în Obiectiv-C:

  1. Trimiteți manual metode pentru a crește / reduce numărul de referințe la un obiect.
  2. Permiteți-vă să lucrați pentru Xcode 4.2 (și mai târziu) noua schemă de numărare automată a referințelor (ARC).

ARC este modul preferat de gestionare a memoriei în aplicații noi, dar este important să înțelegem ce se întâmplă sub capotă. Prima parte a acestui capitol vă arată modul de urmărire manuală a referințelor la obiecte, iar apoi vom vorbi despre implicațiile practice ale ARC.


Gestionarea manuală a memoriei

Pentru a experimenta oricare dintre codurile din această secțiune, va trebui să dezactivați numărarea automată a referințelor. Puteți face acest lucru dând clic pe HelloObjectiveC proiect în panoul de navigare Xcode:

Proiectul HelloObjectiveC din panoul de navigare

Aceasta deschide o fereastră pentru a vă permite să ajustați setările de construire pentru proiect. Vom discuta despre setările de construire în a doua jumătate a acestei serii. Pentru moment, tot ce trebuie să găsim este pavilionul ARC. În câmpul de căutare din colțul din dreapta sus, tastați numărarea automată a referințelor, și ar trebui să vedeți că apare următoarea setare:

Dezactivarea numărării automate a referințelor

Faceți clic pe săgețile de lângă da și schimbați-l Nu pentru a dezactiva ARC pentru acest proiect. Acest lucru vă va permite să utilizați metodele de gestionare a memoriei discutate în paragrafele următoare.

Gestionarea manuală a memoriei (denumită și manuală eliberare de reținere sau MMR) se învârte în jurul conceptului de "proprietate" a obiectului. Când creați un obiect, vi se spune propriu obiectul - este responsabilitatea ta să eliberezi obiectul când termini cu el. Acest lucru are sens, deoarece nu doriți ca alt obiect să vină și să eliberați obiectul în timp ce îl utilizați.

Obținerea proprietății este implementată prin numărarea referințelor. Atunci când pretindeți că sunteți proprietar al unui obiect, creșteți numărul său de referință cu unul, iar atunci când renunțați la proprietate, reduceți numărul de referință al acestuia cu unul. În acest fel, este posibil să se asigure că un obiect nu va fi eliberat din memorie în timp ce un alt obiect îl folosește. NSObject și protocolul NSObject definesc cele patru metode de bază care suportă proprietatea obiectului:

  • +(Id) alloc - Alocați memoria pentru o instanță nouă și revendicați dreptul de proprietate asupra acelei instanțe. Acest lucru crește numărul de referință al obiectului cu unul. Se întoarce un pointer la obiectul alocat.
  • -(Id) reține - Revendicați proprietatea asupra unui obiect existent. Este posibil ca un obiect să aibă mai mulți proprietari. Acest lucru crește, de asemenea, numărul de referință al obiectului. Returnează un indicator la obiectul existent.
  • -(Void) eliberare - Renunțați la proprietatea unui obiect. Acest lucru scade numărul de referință al obiectului.
  • -(Id) AutoreleasePool - Renunțați la proprietatea asupra unui obiect la sfârșitul blocului actual de autorelează. Acest lucru scade numărul de referință al obiectului, dar vă permite să continuați să utilizați obiectul prin amânarea eliberării efective până la un moment ulterior. Returnează un indicator la obiectul existent.

Pentru fiecare aloc sau reține metoda pe care o apelați, trebuie să apelați eliberare sau AutoreleasePool la un moment dat pe linie. De câte ori revendicați un obiect trebuie sa egal de câte ori îl eliberați. Apelarea unui extra aloc/reține va duce la o scurgeri de memorie și va chema un extra eliberare/AutoreleasePool va încerca să acceseze un obiect care nu există, cauzând prăbușirea programului dvs..

Toate interacțiunile obiectului dvs. - indiferent dacă le utilizați într-o metodă de instanță, getter / setter sau o funcție autonomă - ar trebui să urmeze modelul revendicării / utilizării / libere, după cum se demonstrează în următorul exemplu:

Eșantion de cod inclus: memorie manuală

int principal (int argc, const char * argv []) // Revendicați obiectul. Persoana * frank = [[Person alloc] init]; // Utilizați obiectul. frank.name = @ "Frank"; NSLog (@ "% @", frank.name); // Eliberați obiectul. [eliberare sinceră]; retur 0; 

[Person alloc] seturi de apeluri sincernumărul de referință pentru unul, și [eliberare sinceră] scade la zero, permițând runtime-ului să-l elimine. Rețineți că încercați să apelați altul [eliberare sinceră] ar duce la un accident, din moment ce sincer variabila nu mai există în memorie.

Atunci când utilizați obiecte ca variabilă locală într-o funcție (de exemplu, exemplul anterior), gestionarea memoriei este destul de simplă: pur și simplu apelați eliberare la sfârșitul funcției. Cu toate acestea, lucrurile pot deveni mai dificile atunci când se atribuie proprietăți în interiorul metodelor setter. De exemplu, ia în considerare următoarea interfață pentru o nouă clasă numită Navă:

Eșantion de cod inclus: Memorie manuală - referință slabă

// Ship.h #import "Person.h" @interface Nave: NSObject - (Persoană *) căpitan; - (void) setCaptain: (Persoana *) theCaptain; @Sfârșit

Aceasta este o clasă foarte simplă cu metode accesoriale definite manual pentru a căpitan proprietate. Din perspectiva gestionării memoriei, există mai multe moduri în care setterul poate fi implementat. Mai întâi, luați cel mai simplu caz în care noua valoare este atribuită pur și simplu unei variabile de instanță:

// Ship.m #import "Ship.h" @implementation Ship Person * _captain;  - (Persoana *) căpitanul return _captain;  - (void) setCaptain: (Persoana *) theCaptain _captain =Captain;  @Sfârșit

Aceasta creează o referință slabă deoarece Navă instanța nu își asumă dreptul de proprietate asupra căpitanul obiect atunci când devine atribuit. Deși nu este nimic în neregulă cu acest lucru și codul dvs. va funcționa în continuare, este important să înțelegeți implicațiile referințelor slabe. Luați în considerare următorul fragment:

#import  #import "Person.h" #import "Ship.h" int principal (int argc, const char * argv []) @autoreleasepool Persoană * frank = [[Person alloc] init]; Navele * descoperireaOne = [[alocarea navei] init]; frank.name = @ "Frank"; [descoperirea unui setCaptain: franc]; NSLog (@ "% @", [descoperirea unui căpitan] .name); [eliberare sinceră]; // [descoperirea unui căpitan] este acum nevalid. NSLog (@ "% @", [descoperirea unui căpitan]. Nume); [discoveryOne release];  retur 0; 

apel [eliberare sinceră] descreșteri sincernumarul de referinta la zero, ceea ce inseamna ca runtime-ul este permis sa-l aloce. Aceasta înseamnă că [descoperirea unui căpitan] acum indică o adresă de memorie nevalidă, chiar dacă discoveryOne niciodată nu l-au eliberat.

În exemplul de cod furnizat, veți observa că am adăugat o dealloc override în clasa Person. dealloc se numește atunci când memoria urmează să fie eliberată. Ar trebui să ne ocupăm de obicei dealloc și eliberați orice referințe de obiecte imbricate pe care le deținem. În acest caz, vom elibera proprietatea numelui imbricat pe care îl deținem. Vom avea mai multe de spus despre asta dealloc în capitolul următor.

Dacă ați încerca să accesați proprietatea, programul dvs. ar fi cel mai probabil să se prăbușească. După cum puteți vedea, trebuie să fiți foarte atent să urmăriți referințele obiectului când utilizați proprietăți slab raportate.

Referință slabă la valoarea căpitanului

Pentru mai multe relații robuste de obiecte, puteți folosi referințe puternice. Acestea sunt create prin revendicarea obiectului cu a reține apel când este atribuit:

Eșantion de cod inclus: Memorie manuală - referință puternică

- (void) setCaptain: (Persoana *) theCaptain [_captain autorelease]; _captain = [Rezervorul reține]; 

Cu o referință puternică, nu contează ce alte obiecte fac cu căpitanul obiect, deoarece reține va asigura ca va ramane in jur de atata timp cat Navă instanța are nevoie de ea. Desigur, trebuie să echilibrați reține apelând eliberând vechea valoare - dacă nu ați făcut-o, programul dvs. ar pierde memoria atunci când cineva a atribuit o valoare nouă căpitan proprietate.

Referință puternică la valoarea căpitanului

Obiecte auto-eliberare

AutoreleasePool metoda funcționează mult eliberare, cu excepția faptului că numărul de referință al obiectului nu este diminuat imediat. În schimb, timpul de execuție așteaptă până la sfârșitul curentului @autoreleasepool bloc pentru a apela un normal eliberare pe obiect. Acesta este motivul pentru care main.m șablonul este întotdeauna înfășurat într-un @autoreleasepool-se asigură că toate obiectele sunt în coadă AutoreleasePool apelurile sunt de fapt lansat la sfârșitul programului:

int main (int argc, const char * argv []) @autoreleasepool // Introduceti codul pentru a crea si autoreluza obiecte aici. NSLog (@ "Bună ziua, lumea!"); // Orice obiecte autorelează sunt, de fapt, eliberate aici.  retur 0; 

Ideea din spatele auto-eliberării este de a da proprietarului unui obiect capacitatea de a renunța la proprietate fără a distruge de fapt obiectul. Acesta este un instrument necesar în situațiile în care trebuie să returnați un obiect nou dintr-o metodă din fabrică. De exemplu, luați în considerare următoarea metodă de clasă definită în Ship.m:

+ (Nava *) nava CuCaptain: (Persoana * )Captian nava * theShip = [[alocare nava] init]; [setul de încărcăturăCaptain: theCaptian]; retur; 

Această metodă creează, configurează și returnează o nouă metodă Navă instanță. Dar există o problemă gravă cu această implementare: duce la o scurgere de memorie. Metoda nu renunță niciodată la proprietatea asupra obiectului, iar apelanții shipWithCaptain nu știu că trebuie să elibereze obiectul returnat (și nici nu ar trebui să). Ca urmare, nava obiect nu va fi niciodată eliberat din memorie. Aceasta este tocmai situația AutoreleasePool a fost proiectat pentru. Implementarea corectă este prezentată aici:

+ (Nava *) nava CuCaptain: (Persoana * )Captian nava * theShip = [[alocare nava] init]; [setul de încărcăturăCaptain: theCaptian]; reveniți [theShip autorelease]; // Trebuie să renunțe la proprietate! 

Utilizarea AutoreleasePool în loc de un moment eliberare permite apelantului să utilizeze obiectul returnat, în timp ce renunță încă la proprietatea acestuia în locația corectă. Dacă vă amintiți din capitolul Tipuri de date, am creat toate structurile de date ale Fundației utilizând metode de fabrică la nivel de clasă. De exemplu:

NSSet * echipaj = [NSSet setWithObjects: @ "Dave", @ "Heywood", @ "Frank", @ "HAL", nil];

setWithObjects metoda funcționează exact ca shipWithCaptain metoda descrisă în exemplul anterior. Returnează un obiect autorelaționat, astfel încât apelantul să poată utiliza obiectul fără a vă îngrijora de gestionarea memoriei. Rețineți că există metode de instanță echivalente pentru inițializarea obiectelor Foundation. De exemplu, echipaj obiect din ultimul eșantion poate fi creat manual după cum urmează:

// Creați și revendicați setul. NSSet * echipaj = [[NSSet alin] initWithObjects: @ "Dave", @ "Heywood", @ "Frank", @ "HAL", nil]; // Folosiți setul ... // Eliberați setul. [eliberarea echipajului];

Cu toate acestea, folosind metode de clasă cum ar fi setWithObjects, arrayWithCapacity, etc, este, în general, preferată față de aloc/init.

Atributele de menținere-eliberare manuală

A face cu memoria din spatele proprietăților obiectului poate fi o sarcină dificilă, repetitivă. Pentru a simplifica procesul, obiectivul C include mai multe atribute de proprietate pentru automatizarea apelurilor de gestionare a memoriei în funcțiile accesorilor. Atributele descrise în lista următoare definesc comportamentul setterului în manual medii de numărare a referințelor. Do nu incearca sa folosesti atribui și reține într-un mediu de numărare automată a referințelor.

  • atribui - Păstrați un indicator direct la noua valoare fără niciunul reține / eliberare apeluri. Acesta este echivalentul automatizat al unei referințe slabe.
  • reține - Păstrați un indicator direct la noua valoare, dar apelați eliberare pe valoarea veche și reține pe cel nou. Acesta este echivalentul automat al unei referințe puternice.
  • copie - Creați o copie a noii valori. Copierea revendică proprietatea noului exemplu, astfel încât valoarea anterioară este trimisă a eliberare mesaj. Aceasta este ca o referință puternică la o nouă instanță a obiectului. În general, copierea este utilizată numai pentru tipuri imuabile, cum ar fi NSString.

Ca un exemplu simplu, examinați următoarea declarație de proprietate:

@property (păstrează) Persoană * căpitan;

reține atributul le spune asociatului @sintetiza declarație pentru a crea un setter care arată ceva de genul:

- (void) setCaptain: (Persoana *) theCaptain [_ release release]; _captain = [Rezervorul reține]; 

După cum vă puteți imagina, folosiți atributele de gestionare a memoriei cu @proprietate este mult mai ușor decât definirea manuală a getters și setters pentru fiecare proprietate a fiecărei clase personalizate pe care o definiți.


Numărătoarea automată a referințelor

Acum că aveți un mâner pe numărarea referințelor, proprietatea obiectelor și blocurile de autorelează, puteți uita complet de toate acestea. Începând cu Xcode 4.2 și iOS 4, obiectivul C suportă numărarea automată a referințelor (ARC), care este un pas de precompilare care adaugă apelurile necesare pentru managementul memoriei.

Dacă ați dezactivat ARC în secțiunea anterioară, ar trebui să o reporniți. Amintiți-vă că puteți face acest lucru făcând clic pe HelloObjectiveC proiect în panoul de navigare, selectând Construiți setările fila și căutarea numărarea automată a referințelor.

Activarea rapoartelor automate de numărare în setările de construire a proiectului

Numărătoarea automată de referință funcționează examinând codul pentru a afla cât timp un obiect trebuie să rămână în jur și să îl introducă reține, eliberare, și AutoreleasePool metode pentru a vă asigura că este dezalocat atunci când nu mai este necesar, dar nu în timp ce îl utilizați. Pentru a nu confunda algoritmul ARC, tu nu trebuie să face orice reține, eliberare, sau AutoreleasePool te cheamă. De exemplu, cu ARC, puteți scrie următoarea metodă și nici una nava nici căpitanul vor fi scurgeri, chiar dacă nu am renunțat în mod explicit la proprietatea lor:

Eșantion de cod inclus: ARC

+ (Nava *) navă nava * theShip = [[alocarea navei] init]; Persoana * theCaptain = [[Person aloca] init]; [setul de echipajCaptain: captain]; retur; 

Atributele ARC

Într-un mediu ARC, nu mai trebuie să utilizați atribui și reține atributele de proprietate. În schimb, ar trebui să utilizați slab și puternic atribute:

  • slab - Specificați o relație non-proprietară cu obiectul destinație. Acest lucru este foarte asemanator atribui; cu toate acestea, are funcționalitatea convenabilă de a seta proprietatea la zero dacă valoarea este dealocată. În acest fel, programul dvs. nu se va prăbuși când încearcă să acceseze o adresă de memorie invalidă.
  • puternic - Specificați o relație deținută cu obiectul destinație. Acesta este echivalentul ARC reține. Se asigură că un obiect nu va fi eliberat atât timp cât este atribuit proprietății.

Puteți vedea diferența dintre slab și puternic folosind implementarea navă clasă din secțiunea anterioară. Pentru a crea o referință puternică la căpitanul navei, interfața pentru Navă ar trebui să arate după cum urmează:

// Ship.h #import "Person.h" @ nava de intrare: NSObject @property (strong) Person * captain; + (Navă *) navă; @Sfârșit

Și punerea în aplicare Navă ar trebui să arate ca:

// Ship.m #import "Ship.h" @implementation Navează @synthesize captain = _captain; + (Navă *) navă navă * theShip = [[alocare navă] init]; Persoana * theCaptain = [[Person aloca] init]; [setul de echipajCaptain: captain]; retur;  @Sfârșit

Apoi, poți schimba main.m pentru a afișa căpitanul navei:

int principal (int argc, const char * argv []) @autoreleasepool nava * nava = [navă navă]; NSLog (@ "% @", [căpitan de navă]);  retur 0; 

Aceasta va scoate ceva de genul în consola, care ne spune că căpitanul obiect creat în navă metoda de clasă încă mai există.

Dar, încercați să schimbați (puternic) atributul de proprietate la (slab) și re-compilarea programului. Ar trebui să vezi (nul) în panoul de ieșire. Referința slabă nu asigură faptul că căpitanul bastoane variabile în jurul valorii, astfel încât odată ce ajunge la sfârșitul anului navă metoda de clasă, algoritmul ARC crede că poate dispune căpitanul. Ca urmare, căpitan proprietatea este setată la zero.


rezumat

Managementul memoriei poate fi o durere, dar este o parte esențială a construirii unei aplicații. Pentru aplicațiile iOS, alocarea / eliminarea adecvată a obiectelor este deosebit de importantă din cauza resurselor limitate de memorie ale dispozitivelor mobile. Vom vorbi mai multe despre acest lucru în a doua parte a acestei serii, iOS Succinct.

Din fericire, noua schemă ARC face gestionarea memoriei mult mai ușoară pentru dezvoltatorul mediu. În cele mai multe cazuri, este posibil să tratați un proiect ARC la fel ca și colecția de gunoi într-un program C # - doar creați obiectele și lăsați ARC să le dispună la discreția lor. Rețineți, totuși, că aceasta este doar o similitudine practică - implementarea ARC este mult mai eficientă decât colectarea gunoiului.

Această lecție reprezintă un capitol din Obiectiv-C Succinct, o carte electronică gratuită de la echipa de la Syncfusion.
Cod