Welkom bij deel vijf van deze serie over Objective-C. Vandaag gaan we kijken naar geheugenbeheer, een element van Objective-C (en vele andere talen) dat de neiging heeft om nieuwere programmeurs omver te werpen. De meeste scriptingtalen (zoals PHP) zorgen automatisch voor geheugenmanagement, maar Objective-C vereist dat we voorzichtig zijn met ons geheugengebruik en handmatig ruimte creëren en vrijgeven voor onze objecten.
Het is een goede gewoonte om bij te houden hoeveel geheugen uw toepassing gebruikt, zodat u geen lekken of geheugen tegenkomt op het systeem. Het is zelfs nog belangrijker op mobiele systemen zoals de iPhone, waar het geheugen veel beperkter is dan op een desktopcomputer.
In Objective-C zijn er twee methoden voor het beheren van het geheugen, de eerste als referentietelling en de tweede is garbage collection. Je kunt ze beschouwen als handmatig en automatisch, aangezien het tellen van de referentiecode wordt toegevoegd door de programmeur en garbage collection het systeem is dat automatisch ons geheugen beheert. Een belangrijke opmerking is dat garbage collection niet werkt op de iPhone, daarom zullen we niet kijken naar hoe het werkt. Als je zou willen programmeren voor de Mac, dan is het de moeite waard om naar de documentatie van Apple te kijken om te zien hoe garbagecollection werkt.
Dus, hoe beheren we ons geheugen in onze apps? Allereerst, wanneer gebruiken we het geheugen in onze code? Wanneer u een exemplaar van een klasse (een object) maakt, wordt het geheugen toegewezen en kan ons object nu correct functioneren. Nu lijkt een klein object misschien niet zo'n goede deal, maar wanneer je apps in omvang toenemen, wordt het al snel een enorm probleem.
Laten we een voorbeeld bekijken, laten we zeggen dat we een soort tekenapp hebben en dat elke vorm die de gebruiker tekent een afzonderlijk object is. Als de gebruiker 100 vormen heeft getekend, hebben we 100 objecten in het geheugen zitten. Laten we nu zeggen dat de gebruiker opnieuw begint en het scherm wist, en vervolgens nog eens 100 objecten tekent. Als we ons geheugen niet goed beheren, zullen we eindigen met 200 objecten die niets meer doen dan geheugen opdoen.
Dit wordt tegengegaan door referentietelling. Wanneer we een nieuw object maken en gebruik maken van alloc, hebben onze objecten een behoudtelling van 1. Als we behouden dat object behouden, is het aantal behouden nu 2 enzovoort. Als we het object vrijgeven, wordt het aantal keren teruggebracht tot 1. Hoewel het aantal behouden niet-nul is, blijft ons object rondhangen, maar wanneer het aantal behouden waarden nul wordt, wijst het systeem ons object vrij - vrijmaken van het geheugen.
Er zijn verschillende methoden die u kunt gebruiken die enig effect hebben op geheugenbeheer. Allereerst, wanneer u een object maakt met een methode naam die allocatie, nieuw of kopie bevat, neemt u het eigendom van dat object over. Dit geldt ook als u de methode behouden op een object gebruikt. Als je een object eenmaal vrijgeeft, of later weer autorelease geeft (meer daarover later), neem je niet langer het eigendom van dat voorwerp over of zorg je ervoor wat er gebeurt..
Dus als we een object als zodanig toewijzen;
myCarClass * car = [myCarClass alloc];
We zijn nu verantwoordelijk voor de objectauto en we moeten deze later handmatig vrijgeven (of zelf autoreleren). Het is belangrijk op te merken dat als u een object dat is ingesteld op autorelease handmatig zou proberen vrij te geven, de applicatie zou crashen.
Omdat we ons object met behulp van alloc hebben gemaakt, heeft ons auto-object nu een aantal behouden van 1, wat betekent dat het niet zal worden verwijderd. Als we ons object ook zo zouden willen behouden;
[auto behouden];
Dan is onze bewaartelling nu 2. Om het object kwijt te raken, moeten we twee keer vrijgeven om de ontgrendeling op 0 in te stellen. Aangezien het aantal behouden nu nul is, wordt het object de toewijzing ongedaan gemaakt.
Wanneer u een nieuw XCode-project hebt gemaakt, hebt u mogelijk een code opgemerkt die standaard wordt weergegeven en een autorelease-pool maakt, tot nu toe heeft u deze genegeerd. Nu gaan we kijken wat deze doet en waar u deze kunt gebruiken..
De code die u waarschijnlijk al kent, moet er zo uitzien;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; [afvoer zwembad];
Opmerking: als u naar oudere documentatie verwijst, ziet u mogelijk de laatste regel als vrijgave in plaats van als leegloop. Dit is een nieuwere toevoeging aan de taal maar doet in essentie hetzelfde.
Inmiddels zou je tot op zekere hoogte kunnen zien wat de bovenstaande code doet; het maakt een instantie aan van NSAutoReleasePool genaamd pool, wijst er geheugen voor toe en start het vervolgens met behulp van de methode init.
Wanneer we het autoreleasebericht naar een object sturen, wordt dat object vervolgens toegevoegd aan de binnenste automaadgroep (innerlijk meest omdat pools in elkaar kunnen worden genest - later meer hierover). Wanneer de pool de afvoerboodschap wordt verzonden, worden alle objecten die het autoreleasebericht hebben verzonden, vrijgegeven, in wezen stelt autorelease de release uit tot later.
Dit is handig omdat veel methoden die een object retourneren, doorgaans een object met autorelease retourneren, wat betekent dat we ons geen zorgen hoeven te maken over de behouden telling van het object dat we zojuist hebben gekregen. We moeten het ook niet vrijgeven, omdat het later gedaan.
Ik sprak kort over de mogelijkheid om autoreleased pools te nestelen, maar wat hebben we eraan? Hoewel er verschillende toepassingen zijn, is een van de meest voorkomende toepassingen het nesten van een autorelease-pool in een lus die tijdelijke objecten gebruikt.
Als u bijvoorbeeld een lus hebt die twee tijdelijke objecten maakt om te doen wat u wilt, als u deze twee objecten instelt op autorelease, kunt u ze gebruiken totdat de pool de afvoerboodschap heeft ontvangen en u zich geen zorgen hoeft te maken over het handmatig vrijgeven om te delokaliseren. Apple heeft een goed voorbeeld van wanneer je dit soort geneste autorelease-pool in hun documentatie zou gebruiken;
void main () NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSArray * args = [[NSProcessInfo processInfo] -argumenten]; for (NSString * fileName in args) NSAutoreleasePool * loopPool = [[NSAutoreleasePool alloc] init]; NSError * error = nil; NSString * fileContents = [[[NSString alloc] initWithContentsOfFile: bestandsnaam codering: NSUTF8StringEncoding error: & error] autorelease]; / * Verwerk de string, maak en autoreleer meer objecten. * / [loopPool-afvoer]; / * Doe alles wat opruimen nodig is. * / [zwembadafvoer]; exit (EXIT_SUCCESS);
Bron: mmAutoreleasePools
Het bovenstaande voorbeeld heeft iets meer dan we nodig hebben, maar de structuur is er. Zoals u kunt zien, wordt een pool van automatische gegevensverzameling met de naam pool geopend wanneer de toepassing wordt geopend en het hoofdbestand wordt geladen. Wat betekent dat alles wat autoreleased is voordat de pool verzonden is, de drain-boodschap zal worden toegewezen aan deze autorelease-pool, tenzij het zich binnen een autorelease-pool binnenin deze bevindt (sorry als dat een beetje verwarrend klinkt).
Binnen de loop wordt een andere autorelease-pool aangemaakt met de naam loopPool. Dit zwembad wordt binnen de lus gedraineerd, dus alles dat binnen de lus is gelost, wordt vrijgegeven voordat de lus wordt herhaald (of eindigt).
De innerlijke autorelease-pool heeft absoluut geen effect op de buitenste autorelease-pool, je mag zoveel autorelease-pools nesten als je nodig hebt. Als we autorelease in de bovenstaande lus hadden gebruikt, maar geen afzonderlijke autorelease-pool hadden, dan zouden alle objecten die we aan het maken waren niet vrijgegeven worden tot het einde van de main. Dus als de lus 100 keer loopt, hebben we 100 objecten in geheugen die nog moeten worden vrijgegeven - een opgeblazen gevoel van onze applicatie.
Laten we, voordat we afronden, eens kijken naar iets dat kan helpen om van het geheugenbeheer een eenvoudiger te verslinden hoofdstuk te maken. Tot dusverre hebben we, wanneer we objecten hebben gemaakt, onthouden hoeveel referenties een object heeft enzovoort, maar we hebben nog nooit een echt getal gezien. Voor onderwijsdoeleinden is er een methode die we kunnen gebruiken om te zien hoeveel referenties een object retainCount heeft genoemd. De manier waarop we een retainCount voor een object afdrukken, is zoals zo;
NSLog (@ "retainCount voor auto:% d", [car retainCount]);
retainCount retourneert een geheel getal, dus gebruiken we% d om het in de console weer te geven. Er zijn zeldzame gevallen (waar we niet naar toe gaan) waar retainCount verkeerd kan zijn en als zodanig niet 100% op programmabasis moet vertrouwen. Het is alleen geïmplementeerd voor foutopsporing, dus een app mag nooit live gaan met de methode retainCount.
Geheugenbeheer is een onderwerp dat veel nieuwe programmeurs moeilijk vinden, vooral programmeurs die afkomstig zijn uit talen die voor u alles regelen. We hebben de basis besproken, wat genoeg zou moeten zijn om u in staat te stellen uw voeten te vinden en geheugenmanagement in uw apps te integreren.
Apple heeft een fantastische documentatiebibliotheek voor ontwikkelaars beschikbaar op hun website voor ontwikkelaars. Ik raad u ten zeerste aan om uit te zoeken of u onduidelijk bent over alles wat we vandaag hebben aangeroerd. We hebben geprobeerd om de zelfstudie kort en lasergericht te houden om u te helpen geheugenbeheer te begrijpen zonder dat er extra pluis aan is toegevoegd.
Vragen zijn zoals gewoonlijk welkom.
Experimenteer eenvoudig met de console door een eenvoudig object te maken dat een paar gesynthetiseerde variabelen bevat, een paar instanties van deze klasse te maken en vervolgens het aantal behouden te controleren met de methode retainCount. De beste manier om geheugenbeheer te begrijpen is om XCode op te starten en te spelen met toewijzingen en bewaarprogramma's, onthoud dat crashes en fouten geen bakstenen muur zijn, omdat ze u uiteindelijk helpen fouten in de toekomst te voorkomen.
In de volgende aflevering zullen we kijken naar categorieën, een geweldige functie beschikbaar in Objective-C die ontwikkelaars veel tijd kan besparen en zorgen voor meer simplistische code.