Välkommen till del fem i denna serie om mål-C. Idag ska vi titta på minneshantering, ett element av Objective-C (och många andra språk) som tenderar att resa upp nya programmerare. De flesta skriptspråk (till exempel PHP) tar hand om minneshantering automatiskt, men Objective-C kräver att vi är försiktiga med vår användning av minne och manuellt skapa och släppa utrymme för våra objekt.
Det är bra att hålla reda på hur mycket minne ditt program använder, så att du inte stöter på läckor eller svänger upp minnet på systemet. Det är ännu viktigare för mobilsystem som iPhone där minnet är mycket mer begränsat än på en stationär dator.
I Objective-C finns det två metoder för hantering av minnet, det första om referensräkning och det andra är skräpsamling. Du kan tänka på dem som manuella och automatiska, eftersom referensräkning är kod som läggs till av programmeraren och sophämtning är systemet automatiskt hantering av vårt minne. En viktig anmärkning är att insamling av sopor inte fungerar på iPhone, varför vi inte ser på hur det fungerar. Om du skulle vilja programmera till Mac, är det värt att titta på Apples dokumentation för att se hur insamling fungerar.
Så, hur hanterar vi vårt minne i våra appar? Först och främst, när använder vi minne i vår kod? När du skapar en förekomst av en klass (ett objekt), tilldelas minne och vårt objekt kan nu fungera korrekt. Nu verkar ett litet objekt inte vara så bra av en affär, men när dina appar växer i storlek blir det snabbt ett enormt problem.
Låt oss titta på ett exempel, säg att vi har en sorts teckningsapp och varje form som användaren gör är ett separat objekt. Om användaren har ritat 100 former har vi 100 objekt i minnet. Låt oss nu säga att användaren börjar över och rensar skärmen och drar sedan ytterligare 100 objekt. Om vi inte lyckas med vårt minne ordentligt, kommer vi att sluta med 200 objekt som inte gör någonting mer än högt minne.
Vi räknar med det här med referensräkning. När vi skapar ett nytt objekt och använder allokering, har våra objekt ett behållningsantal av 1. Om vi ringer behåll på det objektet är behållningsräkningen nu 2 och så vidare. Om vi släpper objektet minskar behållningsräkningen tillbaka till 1. Medan behållningsräkningen är noll, kommer vårt objekt att hålla fast, men när behållningsräkningen når noll, avkallar systemet vårt objekt - frigör minnet.
Det finns olika metoder du kan ringa som kommer att ha viss effekt på minneshantering. Först och främst när du skapar ett objekt med ett metodnamn som innehåller allokering, ny eller kopiering tar du äganderätt till det objektet. Detta gäller även om du använder behållningsmetoden på ett objekt. När du släpper ut, eller autoreleas (mer om det senare) ett objekt, tar du inte längre äganderätten till objektet eller bryr dig om vad som händer med det.
Så, om vi allokerar ett objekt som så;
myCarClass * car = [myCarClass-allokering];
Vi är nu ansvariga för objektbilen och vi måste manuellt släppa den senare (eller autoreleera den). Det är viktigt att notera att om du skulle försöka manuellt släppa ett objekt som har ställts in till autorelease skulle applikationen krascha.
Eftersom vi skapat vårt objekt med anslag har vårt bilobjekt nu ett antal av 1, vilket betyder att det inte kommer att fördelas. Om skulle behålla vårt föremål som så;
[bibehållen bil]
Då är vårt kvarhållande count nu 2. För att bli av med objektet måste vi släppa två gånger för att ställa in behållningsräkningen till 0. Eftersom behållningsräkningen är noll kommer objektet att fördelas.
När du har skapat ett nytt XCode-projekt kanske du har märkt någon kod som visas som standard som skapar en autorelease-pool, hittills har du ignorerat det - nu ska vi se vad det gör och var du ska använda det.
Koden du förmodligen är bekant med att se nu ska se ut så här;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; [poolavloppet];
Obs! Om du hänvisar till äldre dokumentation kan du kanske se den sista raden som släpp, snarare än avlopp, det här är ett nyare tillägg till språket, men gör i huvudsak samma sak.
Vid det här laget bör du kunna berätta vad koden ovan gör i viss utsträckning; det skapar en instans av NSAutoReleasePool kallad pool, allokerar minne för det och sedan initierar det med hjälp av init-metoden.
När vi skickar autoreleas-meddelandet till ett objekt, läggs det objektet till den inre, mest automatiska frisättningspoolen (innerst, för att poolerna kan nästas i varandra - mer om det senare). När poolen skickas dräneringsmeddelandet, släpps alla objekt som skickats till autoreleasmeddelandet, i huvudsak utlöser autoreleas utlösningen tills senare.
Det här är användbart eftersom många metoder som returnerar ett objekt, returnerar vanligtvis ett autorelerat objekt, vilket innebär att vi inte behöver oroa oss för behållningsräkningen av objektet vi just fått, inte heller måste vi släppa det, eftersom det kommer att bli gjort senare.
Jag pratade kort före om möjligheten att bo i autoelaterade pooler, men vilken användning är det för oss? Även om det finns flera användningsområden, är en av de vanligaste användningarna att nesta en autorelease-pool i en slinga som använder tillfälliga objekt.
Om du till exempel har en slinga som skapar två tillfälliga objekt för att göra vad som helst som du önskar, om du ställer in dessa två objekt till autorelease kan du använda dem tills poolen skickas utmatningsmeddelandet och behöver inte oroa dig för att man manuellt släpper ut att fördela. Apple har ett bra exempel på när du skulle använda denna typ av nestad autorelease pool i deras dokumentation;
void main () NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSArray * args = [[NSProcessInfo processInfo] argument]; för (NSString * filnamn i args) NSAutoreleasePool * loopPool = [[NSAutoreleasePool alloc] init]; NSError * error = nil; NSString * fileContents = [[[NSString alloc] initWithContentsOfFile: filnamnkodning: NSUTF8StringEncoding error: & error] autorelease]; / * Bearbeta strängen, skapa och autoreleasing fler objekt. * / [loopPool drain]; / * Gör vad som helst som behövs. * / [poolavlopp]; utgång (EXIT_SUCCESS);
Källa: mmAutoreleasePools
Ovanstående exempel har lite mer än vad vi behöver, men strukturen finns där. Som du kan se, när programmet öppnas och huvudet laddas, skapas en bilpool kallad pool. Betydande vad som helst auktoriserad innan poolen skickas kommer dräneringsmeddelandet att tilldelas denna autorelease pool, såvida det inte finns inne i en autorelease pool inuti den här (förlåt om det låter lite förvirrande).
Inuti slingan skapas en annan autorelease pool kallad loopPool. Denna pool dräneras inuti slingan, så allting som frigörs inuti slingan släpps innan slingan iterates (eller slutar).
Den inre autoreleaspoolen har absolut ingen effekt på den yttre autoreleaspoolen, du kan bo så många bilar som du behöver. Om vi använde autorelease i slingan ovan, men inte hade en separat autoreleaspool, så skulle alla objekt som vi skapade inte släppas till slutet av huvudet. Så om slingan sprang 100 gånger, skulle vi ha 100 föremål som suger upp minne som ännu inte ska släppas - uppblåst vår ansökan.
Innan vi sätter ihop, låt oss titta på något som kan hjälpa till att göra minneshanteringen ett enklare att svälja kapitel. Hittills när vi har skapat objekt har vi kommit ihåg hur många referenser ett objekt har och så vidare - men vi har aldrig sett ett verkligt tal. För utbildningens syfte finns en metod som vi kan använda för att se hur många referenser ett objekt har kallat retainCount. Det sätt som vi skriver ut en retainCount för ett objekt är som det;
NSLog (@ "keepCount for car:% d", [car retainCount]);
retainCount returnerar ett heltal, så vi använder% d för att visa det i konsolen. Det finns sällsynta fall (som vi inte kommer att gå in på) där retainCount kan vara fel och som sådan borde inte vara 100% beroende av programmässigt. Det är endast implementerat för debugging, så en app borde aldrig gå live med retainCount-metoden.
Minneshantering är ett ämne som många nya programmerare tycker svårt, särskilt programmörer som kommer från språk som tar hand om allt för dig. Vi har täckt grunderna, som bör räcka till för att du ska kunna hitta dina fötter och börja integrera minneshantering i dina appar.
Apple har ett fantastiskt utvecklingsdokumentationsbibliotek tillgängligt på deras utvecklarwebbplats, vilket jag rekommenderar starkt att du kolla om du är oklart på vad vi berörde idag. Vi har försökt att hålla handledningen kort och laserfokuserad idag för att hjälpa dig att förstå minneshantering utan att någon annan fluff läggs till.
Frågor är välkomna, som vanligt.
Prova bara med konsolen genom att skapa ett enkelt objekt som innehåller några syntetiserade variabler, skapa några exempel på den här klassen och kontrollera sedan behållningsräkningen med hjälp av retainCount-metoden. Det bästa sättet att förstå minneshantering är att avfyra XCode och spela runt med allokering och behållning etc, kom ihåg att kraschar och misstag inte är en tegelvägg eftersom de i slutändan hjälper dig att undvika misstag i framtiden.
I nästa avdelning tittar vi på kategorier, en bra funktion som finns tillgänglig i Objective-C som kan spara utvecklare mycket tid och göra för mer förenklad kod.