Benvenuti alla quinta parte di questa serie su Objective-C. Oggi esamineremo la gestione della memoria, un elemento di Objective-C (e di molti altri linguaggi) che tende a far scattare nuovi programmatori. La maggior parte dei linguaggi di scripting (come PHP) si occupa automaticamente della gestione della memoria, ma Objective-C richiede che siamo attenti con il nostro uso della memoria e creiamo e rilasciamo manualmente lo spazio per i nostri oggetti.
È buona norma tenere traccia della quantità di memoria utilizzata dall'applicazione, in modo da evitare perdite o sovraccaricare la memoria del sistema. È ancora più importante su sistemi mobili come l'iPhone, in cui la memoria è molto più limitata rispetto a una macchina desktop.
In Objective-C ci sono due metodi per la gestione della memoria, il primo se il conteggio dei riferimenti e il secondo è la garbage collection. Puoi considerarli come manuali e automatici, poiché il conteggio dei riferimenti è un codice aggiunto dal programmatore e la raccolta dei dati inutili è il sistema che gestisce automaticamente la nostra memoria. Una nota importante è che la garbage collection non funziona su iPhone, motivo per cui non guarderemo come funziona. Se si desidera programmare per il Mac, vale la pena guardare la documentazione di Apple per vedere come funziona la garbage collection.
Quindi, come gestiamo la nostra memoria nelle nostre app? Prima di tutto, quando usiamo la memoria nel nostro codice? Quando crei un'istanza di una classe (un oggetto), la memoria viene allocata e il nostro oggetto può ora funzionare correttamente. Ora un piccolo oggetto potrebbe non sembrare un grande affare, ma quando le tue app crescono di dimensioni, diventa rapidamente un enorme problema.
Diamo un'occhiata a un esempio, diciamo che abbiamo una sorta di app di disegno e ogni forma disegnata dall'utente è un oggetto separato. Se l'utente ha disegnato 100 forme, allora abbiamo 100 oggetti in memoria. Ora diciamo che l'utente ricomincia e cancella lo schermo, quindi disegna altri 100 oggetti. Se non gestiamo correttamente la nostra memoria, finiremo con 200 oggetti che non fanno altro che memoria hogging.
Noi contrastiamo questo con il conteggio dei riferimenti. Quando creiamo un nuovo oggetto e usiamo alloc, i nostri oggetti hanno un conteggio di ritenzione di 1. Se chiamiamo retain su quell'oggetto, il conteggio dei ritardi è ora 2 e così via. Se rilasciamo l'oggetto, il conteggio dei ritiri diminuisce a 1. Mentre il conteggio dei ritiri è diverso da zero, il nostro oggetto resterà fermo, ma quando il conteggio dei ritiri raggiunge lo zero, il sistema rilascia il nostro oggetto - liberando la memoria.
Ci sono vari metodi che puoi chiamare che avranno qualche effetto sulla gestione della memoria. Prima di tutto, quando crei un oggetto usando un nome di metodo che contiene alloc, new o copy, prendi possesso di quell'oggetto. Questo vale anche se si utilizza il metodo retain su un oggetto. Una volta rilasciato o autorizzato (più su quello successivo) un oggetto, non si diventa più proprietari di quell'oggetto o si cura di ciò che accade ad esso.
Quindi, se assegniamo un oggetto in questo modo;
myCarClass * car = [myCarClass alloc];
Ora siamo responsabili per la macchina oggetto e dobbiamo rilasciarlo manualmente in seguito (o autorizzarlo automaticamente). È importante notare che se si tentasse di rilasciare manualmente un oggetto che era stato impostato su autorelease, l'applicazione si arrestava in modo anomalo.
Poiché abbiamo creato il nostro oggetto usando alloc, il nostro oggetto auto ora ha un conteggio di ritenzione pari a 1, il che significa che non sarà deallocato. Se dovessero anche conservare il nostro oggetto in questo modo;
[fermo macchina];
Quindi il conteggio dei ritardi è ora 2. Quindi, per liberarci dell'oggetto, dobbiamo rilasciarlo due volte per impostare il conteggio dei ritardi su 0. Poiché il conteggio dei ritegni è ora zero, l'oggetto sarà deallocato.
Quando hai creato un nuovo progetto XCode, potresti aver notato un codice che appare per impostazione predefinita che crea un pool di autorelease, fino ad ora lo hai ignorato - ora vedremo cosa fa e dove usarlo.
Il codice che probabilmente conosci già adesso dovrebbe assomigliare a questo;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; [scarico piscina];
Nota: se si fa riferimento alla documentazione precedente, è possibile visualizzare l'ultima riga come release, piuttosto che drenare, questa è un'aggiunta più recente al linguaggio ma essenzialmente fa la stessa cosa.
A questo punto dovresti essere in grado di dire in una certa misura cosa sta facendo il codice sopra; sta creando un'istanza di NSAutoReleasePool chiamato pool, allocandone memoria e quindi avviandolo utilizzando il metodo init.
Quando inviamo il messaggio di autorelease a un oggetto, quell'oggetto viene quindi aggiunto al pool di rilascio automatico più interno (più interno perché i pool possono essere annidati l'uno dentro l'altro - ne riparleremo più avanti). Quando al pool viene inviato il messaggio drain, quindi vengono rilasciati tutti gli oggetti inviati il messaggio di autorelease, in sostanza l'autorelease posticipa il rilascio fino a dopo.
Questo è utile perché molti metodi che restituiscono un oggetto, in genere restituiscono un oggetto autoreleased, il che significa che non dobbiamo preoccuparci del conteggio del mantenimento dell'oggetto appena ricevuto, né dobbiamo rilasciarlo, poiché sarà fatto più tardi.
Prima ho parlato brevemente della capacità di nidificare le piscine autorizzate, ma a che cosa serve questo? Sebbene ci siano diversi usi, uno degli usi più comuni è quello di nidificare un pool di autorelease all'interno di un ciclo che utilizza oggetti temporanei.
Ad esempio, se hai un loop che crea due oggetti temporanei per fare quello che desideri, se imposti questi due oggetti per autorelease, puoi usarli finché il pool non viene inviato il messaggio drain e non devi preoccuparti di rilasciare manualmente per deallocare. Apple ha un ottimo esempio di quando si utilizza questo tipo di pool di autorelease nidificati nella propria documentazione;
void main () NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSArray * args = [[NSProcessInfo processInfo] argomenti]; for (NSString * fileName in args) NSAutoreleasePool * loopPool = [[NSAutoreleasePool alloc] init]; NSError * error = nil; NSString * fileContents = [[[NSString alloc] initWithContentsOfFile: codifica fileName: NSUTF8StringEncoding errore: & error] autorelease]; / * Elabora la stringa, creando e autoreleasing più oggetti. * / [loopPool drain]; / * Fai qualsiasi pulizia è necessaria. * / [scarico piscina]; exit (EXIT_SUCCESS);
Fonte: mmAutoreleasePools
L'esempio sopra ha un po 'più di quello di cui abbiamo bisogno, ma la struttura è lì. Come puoi vedere, quando l'applicazione viene aperta e viene caricato main, viene creato un pool di autorelease chiamato pool. Significato di qualsiasi cosa autorizzata prima che il pool venga inviato, il messaggio di drain verrà assegnato a questo pool di autorelease, a meno che non sia all'interno di un pool di autorelease all'interno di questo (scusa se questo suona un po 'di confusione).
All'interno del ciclo, viene creato un altro pool di autorelease chiamato loopPool. Questo pool viene svuotato all'interno del ciclo, quindi qualsiasi cosa autorelitta all'interno del ciclo viene rilasciata prima che il ciclo itera (o termina).
Il pool di autorelease interno non ha assolutamente alcun effetto sul pool di autorelease esterno, è possibile nidificare quanti pool di autorelease sono necessari. Se avessimo usato autorelease nel ciclo sopra, ma non avessimo un pool di autorelease separato, allora tutti gli oggetti che stavamo creando non sarebbero stati rilasciati fino alla fine del main. Quindi, se il ciclo fosse eseguito 100 volte, avremmo 100 oggetti che memorizzarono la memoria che non è stata ancora rilasciata, gonfiando la nostra applicazione.
Prima di concludere, diamo un'occhiata a qualcosa che potrebbe rendere la gestione della memoria un capitolo più facile da digerire. Finora, quando abbiamo creato oggetti, abbiamo ricordato quanti riferimenti ha un oggetto e così via, ma non abbiamo mai visto un numero reale. Ai fini dell'educazione, esiste un metodo che possiamo usare per vedere quanti riferimenti un oggetto ha chiamato retainCount. Il modo in cui stampiamo un retainCount per un oggetto è come tale;
NSLog (@ "retainCount per auto:% d", [car retainCount]);
retainCount restituisce un intero, quindi usiamo% d per visualizzarlo nella console. Ci sono casi rari (ai quali non entreremo) in cui retainCount può essere sbagliato e in quanto tale non dovrebbe essere considerato al 100% su base programmatica. È implementato solo per il debug, quindi un'app non dovrebbe mai essere pubblicata utilizzando il metodo retainCount.
La gestione della memoria è un argomento che molti nuovi programmatori trovano difficile, specialmente i programmatori che provengono da lingue che si occupano di tutto ciò che fa per voi. Abbiamo coperto le nozioni di base, che dovrebbero essere sufficienti per consentirti di trovare i tuoi piedi e iniziare a incorporare la gestione della memoria nelle tue app.
Apple ha una fantastica libreria di documentazione per sviluppatori disponibile sul loro sito web per sviluppatori, che consiglio vivamente di verificare se non sei chiaro su tutto ciò che abbiamo toccato oggi. Abbiamo cercato di mantenere il tutorial breve e focalizzato al laser oggi per aiutarti a capire la gestione della memoria senza aggiungere altro.
Le domande sono benvenute, come al solito.
È sufficiente sperimentare la console creando un oggetto semplice che contenga alcune variabili sintetizzate, creare alcune istanze di questa classe e quindi controllare il conteggio dei ritiri utilizzando il metodo retainCount. Il modo migliore per comprendere la gestione della memoria è attivare XCode e giocare con alloc e retain, ecc. Ricorda che crash e errori non sono un muro di mattoni, in quanto alla fine ti aiuteranno a evitare gli errori in futuro.
Nella prossima puntata vedremo le categorie, una grande funzionalità disponibile in Objective-C che può far risparmiare agli sviluppatori molto tempo e creare codice più semplice.