Objective-C Succinctly Memory Management

Minne måste tilldelas för varje objekt som din ansökan använder och det måste fördelas när din ansökan görs med den för att säkerställa att din ansökan använder minnet så effektivt som möjligt. Det är viktigt att förstå Objective-C: s minnehanteringsmiljö för att säkerställa att ditt program inte läcker minne eller försöker referera till objekt som inte längre existerar.

Räkna referenser till ett objekt

Till skillnad från C #, gör Objective-C inte använd skräpsamling. Istället använder den en referensräknande miljö som spårar hur många platser som använder ett objekt. Så länge det finns åtminstone en referens till objektet, säkerställer objektiv-C-runtime att objektet kommer att ligga i minnet. Men om det inte längre finns några referenser till objektet får körtiden frigöra objektet och använda minnet för något annat. Om du försöker komma åt ett objekt efter det att det släppts kommer ditt program sannolikt att krascha.

Det finns två ömsesidigt exklusiva sätt att hantera objektreferenser i mål-C:

  1. Skicka manuellt metoder för att öka / minska antalet referenser till ett objekt.
  2. Låt Xcode 4.2 (och senare) nya automatisk referensräkning (ARC) -schema göra jobbet för dig.

ARC är det bästa sättet att hantera minne i nya applikationer, men det är fortfarande viktigt att förstå vad som händer under huven. Den första delen av det här kapitlet visar hur man manuellt spårar objektreferenser, och sedan pratar vi om de praktiska konsekvenserna av ARC.


Manuell minneshantering

För att experimentera med någon av koden i det här avsnittet måste du stänga av automatisk referensräkning. Du kan göra detta genom att klicka på HelloObjectiveC projekt i Xcodes navigeringspanel:

HelloObjectiveC-projektet i navigeringspanelen

Detta öppnar ett fönster för att låta dig justera byggnadsinställningarna för projektet. Vi diskuterar byggnadsinställningar under andra halvan av denna serie. För närvarande är allt vi behöver hitta ARC-flaggan. I sökfältet i övre högra hörnet skriver du automatisk referensräkning, och du bör se följande inställning visas:

Inaktiverar automatisk referensräkning

Klicka på pilarna bredvid Ja och ändra den till Nej för att inaktivera ARC för detta projekt. Detta låter dig använda minneshanteringsmetoderna som diskuteras i följande punkter.

Manuell minneshantering (även kallad manuell frigivning eller MMR) kretsar kring begreppet objekt "ägande". När du skapar ett objekt, sägs du till egen objektet - det är ditt ansvar att frigöra objektet när du är färdig med det. Det här är vettigt eftersom du inte vill ha något annat objekt att komma med och släppa objektet medan du använder det.

Objekt ägande implementeras genom referensräkning. När du hävdar äganderätt till ett objekt ökar du dess referensräkning med en, och när du avstår från ägandet, sänker du dess referensräkning med en. På det här sättet är det möjligt att se till att ett objekt aldrig blir befriat från minnet medan ett annat objekt använder det. NSObject och NSObject-protokollet definierar de fyra kärnmetoderna som stöder objektägande:

  • +(Id) alloc - Tilldela minne för en ny instans och hävda äganderätten till det exemplet. Detta ökar objektets referensräkning med en. Den returnerar en pekare till det tilldelade objektet.
  • -(Id) bibehåller - Hävda äganderätt till ett befintligt objekt. Det är möjligt att ett objekt har mer än en ägare. Detta ökar också objektets referensräkning. Den returnerar en pekare till det befintliga objektet.
  • -(Void) frisättning - Avstå äganderätt till ett objekt. Detta minskar objektets referensräkning.
  • -(Id) funktionen för automatisk - Avstå äganderätten till ett objekt i slutet av det aktuella autorelease poolblocket. Detta minskar objektets referensräkning men låter dig fortsätta använda objektet genom att skjuta upp den aktuella utgåvan till en senare tidpunkt. Den returnerar en pekare till det befintliga objektet.

För varje alloc eller behålla Metod du ringer, du måste ringa släpp eller funktionen för automatisk vid någon punkt ner linjen. Antalet gånger du hävdar ett objekt måste lika många gånger du släpper ut det. Ringer en extra alloc/behålla kommer att leda till minnesläckage och ringa extra släpp/funktionen för automatisk kommer att försöka få åtkomst till ett objekt som inte existerar, vilket orsakar att ditt program kraschar.

Alla dina objektinteraktioner - oavsett om du använder dem i en instansmetod, getter / setter eller en fristående funktion - bör följa kravet / använd / fritt mönster, vilket visas i följande exempel:

Inkluderat kodprov: Manuell minne

int main (int argc, const char * argv []) // Påstå objektet. Person * frank = [[Personallokering] init]; // Använd objektet. frank.name = @ "Frank"; NSLog (@ "% @", frank.name); // Frigör objektet. [frank release]; returnera 0; 

De [Personallokering] samtalsuppsättningar frankreferensräkning till en, och [frank release] sänker det till noll, vilket gör att körtiden kan kasseras. Observera att du försöker ringa en annan [frank release] skulle resultera i en krasch, sedan frank variabel finns inte längre i minnet.

När du använder objekt som en lokal variabel i en funktion (t ex föregående exempel) är minneshanteringen ganska enkel: ring bara släpp i slutet av funktionen. Men saker kan bli svårare när man tilldelar egenskaper inom setter metoder. Tänk på följande gränssnitt för en ny klass som heter Fartyg:

Inkluderat kodprov: Manuell minne - svag referens

// Ship.h #import "Person.h" @interface Ship: NSObject - (Person *) kapten; - (void) setCaptain: (Person *) theCaptain; @slutet

Detta är en mycket enkel klass med manuellt definierade accessor metoder för a kapten fast egendom. Ur ett minnehanteringsperspektiv finns det flera sätt som setter kan implementeras. Först, ta det enklaste fallet där det nya värdet helt enkelt tilldelas en instansvariabel:

// Ship.m #import "Ship.h" @implementation Fartyg Person * _captain;  - (Person *) kapten return _captain;  - (void) setCaptain: (Person *) theCaptain _captain = theCaptain;  @slutet

Detta skapar en svag referens eftersom det Fartyg exemplet tar inte ägande av kaptenen objekt när det tilldelas Medan det inte finns något fel med detta, och din kod fortfarande fungerar, är det viktigt att förstå konsekvenserna av svaga referenser. Tänk på följande kod:

#importera  #import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @autoreleasepool Person * frank = [[Personallokering] init]; Fartyg * discoveryOne = [[Ship alloc] init]; frank.name = @ "Frank"; [discoveryOne setCaptain: frank]; NSLog (@ "% @", [discoveryOne captain] .name); [frank release]; // [discoveryOne captain] är nu ogiltig. NSLog (@ "% @", [discoveryOne kapten]. Namn); [discoveryOne release];  returnera 0; 

Kallelse [frank release] minskningar frankreferensräkning till noll, vilket betyder att runtime tillåts att fördela den. Detta innebär att [discoveryOne kapten] pekar nu på en ogiltig minnesadress, även om discoveryOne släppte aldrig det.

I den angivna provkoden kommer du att observera att vi har lagt till en dealloc metodöverstyrning i personklassen. dealloc kallas när minnet håller på att släppas. Vi bör vanligtvis hantera dealloc och släpp alla nestade objektreferenser som vi håller. I detta fall kommer vi att släppa den nestade namnegenskapen som vi håller. Vi får mer att säga om dealloc i nästa kapitel.

Om du skulle försöka komma åt fastigheten skulle ditt program sannolikt krascha. Som du kan se måste du vara mycket noggranna spårningsobjektreferenser när du använder svagt refererade egenskaper.

Svag referens till kaptenvärdet

För mer robusta objektrelationer kan du använda starka referenser. Dessa skapas genom att hävda objektet med a behålla ring när den är tilldelad:

Inkluderat kodprov: Manuell minne - stark referens

- (void) setCaptain: (Person *) theCaptain [_captain autorelease]; _captain = [Kapten behåller]; 

Med en stark referens spelar det ingen roll vad andra objekt gör med kaptenen objekt, eftersom behålla ser till att det kommer att stanna kvar så länge som Fartyg exemplet behöver det. Självklart måste du balansera behålla ring genom att släppa det gamla värdet - om du inte gjorde det, skulle ditt program läcka minne när någon tilldelade ett nytt värde till kapten fast egendom.

Stark hänvisning till kaptenvärdet

Auto-Releasing Objects

De funktionen för automatisk Metoden fungerar mycket som släpp, förutom att objektets referensräkning inte minskas omedelbart. Istället väntar runtime till slutet av strömmen @autoreleasepool blockera för att ringa en normal släpp på objektet. Det är därför main.m mall är alltid insvept i en @autoreleasepool-Det säkerställer att alla föremål är i kö med funktionen för automatisk samtal är faktiskt släppt i slutet av programmet:

int main (int argc, const char * argv []) @autoreleasepool // Infoga kod för att skapa och autoreleera objekt här. NSLog (@ "Hej, Värld!"); // Alla auktoriserade objekt är * faktiskt * släppt här.  returnera 0; 

Tanken bakom auto-releasing är att ge objektets ägare möjlighet att avstå från ägande utan att förstöra objektet. Detta är ett nödvändigt verktyg i situationer där du måste returnera ett nytt objekt från en fabriksmetod. Tänk på följande klassmetod som definieras i Ship.m:

+ (Ship *) shipWithCaptain: (Person *) theCaptian Ship * theShip = [[Ship alloc] init]; [Skytten sättaKaptain: TheCaptian]; returnera shipen; 

Den här metoden skapar, konfigurerar och returnerar en ny Fartyg exempel. Men det finns ett allvarligt problem med denna implementering: det resulterar i en minnesläcka. Metoden avstår aldrig ägande av objektet och uppringare av shipWithCaptain vet inte att de behöver frigöra det återlämnade objektet (ej heller borde de behöva). Som ett resultat av detta fartyget objekt kommer aldrig att släppas från minnet. Detta är just situationen funktionen för automatisk designades för. Det korrekta genomförandet visas här:

+ (Ship *) shipWithCaptain: (Person *) theCaptian Ship * theShip = [[Ship alloc] init]; [Skytten sättaKaptain: TheCaptian]; returnera [theShip autorelease]; // Måste avstå äganderätt! 

Använder sig av funktionen för automatisk istället för omedelbar släpp låter uppringaren använda det återvändande objektet samtidigt som han lämnar äganderätten till det på rätt plats. Om du kommer ihåg från kapitlet Datatyper skapade vi alla våra grunddata-strukturer genom att använda fabriksmetoder på klassnivå. Till exempel:

NSSet * besättning = [NSSet setWithObjects: @ "Dave", @ "Heywood", @ "Frank", @ "HAL", noll];

De setWithObjects Metoden fungerar precis som shipWithCaptain metod som beskrivits i föregående exempel. Den returnerar ett auktoriserat objekt så att den som ringer kan använda objektet utan att oroa sig för minneshantering. Observera att det finns motsvarande instansmetoder för att initiera Foundation-objekt. Till exempel, besättning objekt i det sista provet kan skapas manuellt enligt följande:

// Skapa och hävda uppsättningen. NSSet * besättning = [[NSSet alloc] initWithObjects: @ "Dave", @ "Heywood", @ "Frank", @ "HAL", noll]; // Använd uppsättningen ... // Släpp satsen. [besättningsläge];

Men använder klassmetoder som setWithObjects, arrayWithCapacity, etc., är generellt föredragen över alloc/i det.

Manuell Retain-Release Attribut

Att hantera minnet bakom objektets egenskaper kan vara en tråkig och upprepad uppgift. För att förenkla processen innehåller Objective-C flera egenskapsattribut för automatisering av minneshanteringssamtal i accessorfunktioner. De attribut som beskrivs i följande lista definierar setterbeteendet i manuell referensräknande miljöer. Do inte försök använda tilldela och behålla i en automatisk referensräkningsmiljö.

  • tilldela - Spara en direkt pekare till det nya värdet utan något behålla / släpp samtal. Detta är den automatiska motsvarigheten till en svag referens.
  • behålla - Spara en direkt pekare till det nya värdet, men ring släpp på det gamla värdet och behålla på den nya. Detta är den automatiska motsvarigheten till en stark referens.
  • kopia - Skapa en kopia av det nya värdet. Kopiering fordringar ägande av den nya instansen, så det föregående värdet skickas a släpp meddelande. Det här är som en stark referens till en helt ny förekomst av objektet. I allmänhet används kopiering endast för oföränderliga typer som NSString.

Som ett enkelt exempel, undersök följande fastighetsdeklaration:

@property (behåll) Person * kapten;

De behålla attributet berättar de associerade @synthesize deklaration för att skapa en setter som ser ut som:

- (void) setCaptain: (Person *) theCaptain [_captain release]; _captain = [Kapten behåller]; 

Som du kan föreställa dig, använder du minneshanteringsattribut med @fast egendom är mycket enklare än att manuellt definiera getters och setters för varje egendom i varje anpassad klass du definierar.


Automatisk referensräkning

Nu när du har ett handtag på referensräkning, objektägande och autorelease-block kan du helt glömma allt. När det gäller Xcode 4.2 och iOS 4, stöder Objective-C automatisk referensräkning (ARC), vilket är ett församlingssteg som lägger till i nödvändiga minneshanteringssamtal för dig.

Om du råkade ha stängt av ARC i föregående avsnitt ska du sätta på den igen. Kom ihåg att du kan göra det genom att klicka på HelloObjectiveC projekt i navigeringspanelen, välj Bygg inställningar fliken och letar efter automatisk referensräkning.

Aktiverar automatisk referensräkning i projektets bygginställningar

Automatisk referensräkning fungerar genom att undersöka din kod för att räkna ut hur länge ett objekt behöver hålla sig fast och infoga behålla, släpp, och funktionen för automatisk metoder för att säkerställa att det avlöps när det inte längre behövs, men inte när du använder det. För att inte förväxla ARC-algoritmen, du får inte göra några behålla, släpp, eller funktionen för automatisk kallar dig själv. Till exempel med ARC kan du skriva följande metod och inte heller fartyget inte heller kaptenen kommer att läckas, även om vi inte uttryckligen avstår från ägande av dem:

Inkluderat kodprov: ARC

+ (Ship *) fartyg Ship * theShip = [[Ship alloc] init]; Person * theCaptain = [[Personallokering] init]; [Skytten satt Kapten: Kapten]; returnera shipen; 

ARC Attribut

I en ARC-miljö borde du inte längre använda tilldela och behålla egendom attribut. I stället borde du använda svag och stark attribut:

  • svag - Ange ett icke-ägande förhållande till destinationsobjektet. Det här är ungefär lika tilldela; Det har dock den praktiska funktionaliteten att fastställa egenskapen till noll om värdet är fördelat. På detta sätt kommer ditt program inte att krascha när det försöker komma åt en ogiltig minnesadress.
  • stark - Ange ett ägandeförhållande till destinationsobjektet. Detta är ARC-ekvivalent av behålla. Det säkerställer att ett objekt inte kommer att släppas så länge det tilldelas fastigheten.

Du kan se skillnaden mellan svag och stark med hjälp av implementeringen av fartyg klassmetod från föregående avsnitt. För att skapa en stark referens till fartygets kapten, gränssnittet för Fartyg ska se ut som följande:

// Ship.h #import "Person.h" @interface Ship: NSObject @property (stark) Person * kapten; + (Ship *) fartyg; @slutet

Och genomförandet Fartyg ska se ut som:

// Ship.m #import "Ship.h" @implementation Ship @synthesize captain = _captain; + (Ship *) fartyg Ship * theShip = [[Ship alloc] init]; Person * theCaptain = [[Personallokering] init]; [Skytten satt Kapten: Kapten]; returnera shipen;  @slutet

Då kan du ändra main.m för att visa fartygets kapten:

int main (int argc, const char * argv []) @autoreleasepool Ship * ship = [Ship ship]; NSLog (@ "% @", [fartygs kapten]);  returnera 0; 

Detta kommer att mata ut något liknande i konsolen, som berättar för oss att kaptenen objekt skapat i fartyg klassmetoden finns fortfarande.

Men försök att ändra (stark) egenskap attribut till (svag) och omregistrera programmet. Nu ska du se (null) i utmatningspanelen. Den svaga referensen garanterar inte att kaptenen variabla pinnar runt, så en gång det kommer i slutet av fartyg klassmetoden anser ARC-algoritmen att den kan hantera kaptenen. Som ett resultat av detta kapten fastigheten är inställd på noll.


Sammanfattning

Minneshantering kan vara en smärta, men det är en viktig del av att bygga en applikation. För iOS-applikationer är det lämpligt att tilldela / avyttra objektet på grund av de begränsade minnesresurserna hos mobila enheter. Vi talar mer om detta i den andra delen av denna serie, IOS Succinctly.

Lyckligtvis gör det nya ARC-systemet minnehanteringen mycket lättare på den genomsnittliga utvecklaren. I de flesta fall är det möjligt att behandla ett ARC-projekt precis som skräpuppsamlingen i ett C # -program. Skapa bara dina objekt och låt ARC sköta dem efter eget gottfinnande. Observera dock att det här bara är en praktisk likhet - ARC-genomförandet är mycket effektivare än skräpsamling.

Denna lektion representerar ett kapitel från Objective-C Succinctly, en gratis eBook från laget vid Syncfusion.