Comprensione del conteggio dei riferimenti con Cocoa e Objective-C


122

Sto appena iniziando a dare un'occhiata a Objective-C e Cocoa per giocare con l'iPhone SDK. Sono abbastanza a mio agio con le C malloce il freeconcetto, ma lo schema di conteggio dei riferimenti di Cocoa mi ha piuttosto confuso. Mi è stato detto che è molto elegante una volta capito, ma non ho ancora superato la gobba.

Come fanno release, retaine autoreleaseil lavoro e quali sono le convenzioni circa il loro uso?

(O in caso contrario, cosa hai letto che ti ha aiutato a ottenerlo?)

Risposte:


148

Cominciamo con retaine release; autoreleaseè davvero solo un caso speciale una volta compresi i concetti di base.

In Cocoa, ogni oggetto tiene traccia di quante volte viene fatto riferimento (in particolare, la NSObjectclasse base lo implementa). Richiamando retainun oggetto, gli dici che vuoi aumentare il suo conteggio dei riferimenti di uno. Chiamando release, comunichi all'oggetto che lo stai lasciando andare e il suo conteggio dei riferimenti viene diminuito. Se, dopo la chiamata release, il conteggio dei riferimenti è ora zero, la memoria di quell'oggetto viene liberata dal sistema.

Il modo di base in cui questo è diverso malloced freeè che un dato oggetto non deve preoccuparsi del crash di altre parti del sistema perché hai liberato la memoria che stavano usando. Supponendo che tutti stiano giocando e conservando / rilasciando secondo le regole, quando un pezzo di codice conserva e poi rilascia l'oggetto, qualsiasi altro pezzo di codice che fa riferimento all'oggetto non verrà influenzato.

Ciò che a volte può creare confusione è conoscere le circostanze in cui dovresti chiamare retaine release. La mia regola generale è che se voglio rimanere aggrappato a un oggetto per un certo periodo di tempo (se si tratta di una variabile membro in una classe, per esempio), allora devo assicurarmi che il conteggio dei riferimenti dell'oggetto sappia di me. Come descritto sopra, il conteggio dei riferimenti di un oggetto viene incrementato chiamando retain. Per convenzione, viene anche incrementato (impostato a 1, in realtà) quando l'oggetto viene creato con un metodo "init". In entrambi i casi, è mia responsabilità richiamare releasel'oggetto quando ho finito. Se non lo faccio, ci sarà una perdita di memoria.

Esempio di creazione di oggetti:

NSString* s = [[NSString alloc] init];  // Ref count is 1
[s retain];                             // Ref count is 2 - silly
                                        //   to do this after init
[s release];                            // Ref count is back to 1
[s release];                            // Ref count is 0, object is freed

Adesso per autorelease . Autorelease è usato come un modo comodo (e talvolta necessario) per dire al sistema di liberare questo oggetto dopo un po 'di tempo. Dal punto di vista idraulico, quando autoreleaseviene chiamato, il thread corrente NSAutoreleasePoolviene avvisato della chiamata. L' NSAutoreleasePoolora sa che una volta che si ottiene l'opportunità (dopo l'iterazione corrente del ciclo di eventi), si può chiamare releasesull'oggetto. Dal nostro punto di vista di programmatori, si prende cura di releasechiamarci, quindi non dobbiamo (e in effetti, non dovremmo).

Ciò che è importante notare è che (di nuovo, per convenzione) tutta la creazione di oggetti metodi della classe di restituiscono un oggetto rilasciato automaticamente. Ad esempio, nel seguente esempio, la variabile "s" ha un conteggio dei riferimenti pari a 1, ma una volta completato il ciclo di eventi, verrà distrutto.

NSString* s = [NSString stringWithString:@"Hello World"];

Se vuoi aggrapparti a quella stringa, devi chiamarla retainesplicitamente e poi esplicitamente releasequando hai finito.

Considera il seguente bit di codice (molto artificioso) e vedrai una situazione in cui autorelease è richiesto:

- (NSString*)createHelloWorldString
{
    NSString* s = [[NSString alloc] initWithString:@"Hello World"];

    // Now what?  We want to return s, but we've upped its reference count.
    // The caller shouldn't be responsible for releasing it, since we're the
    // ones that created it.  If we call release, however, the reference 
    // count will hit zero and bad memory will be returned to the caller.  
    // The answer is to call autorelease before returning the string.  By 
    // explicitly calling autorelease, we pass the responsibility for
    // releasing the string on to the thread's NSAutoreleasePool, which will
    // happen at some later time.  The consequence is that the returned string 
    // will still be valid for the caller of this function.
    return [s autorelease];
}

Mi rendo conto che tutto questo crea un po 'di confusione, ma a un certo punto farà clic. Ecco alcuni riferimenti per iniziare:

  • Introduzione di Apple alla gestione della memoria.
  • Cocoa Programming per Mac OS X (4a edizione) , di Aaron Hillegas - un libro molto ben scritto con molti ottimi esempi. Sembra un tutorial.
  • Se ti stai davvero immergendo, potresti andare al Big Nerd Ranch . Questa è una struttura di formazione gestita da Aaron Hillegas, l'autore del libro sopra menzionato. Ho frequentato il corso Intro to Cocoa lì diversi anni fa ed è stato un ottimo modo per imparare.

8
Hai scritto: "Chiamando il rilascio automatico, aumentiamo temporaneamente il conteggio dei riferimenti". Penso che questo sia sbagliato; autorelease contrassegna solo l'oggetto che verrà rilasciato in futuro, non aumenta il conteggio dei ref: cocoadev.com/index.pl?AutoRelease
LKM

2
"Ora per il rilascio automatico. Autorelease è usato come un modo comodo (e talvolta necessario) per dire al sistema di liberare questo oggetto dopo un po '." Come frase introduttiva, questo è sbagliato. Non dice al sistema di "liberarlo", gli dice di diminuire il conteggio di conservazione.
mmalc

3
Grazie mille per la buona spiegazione. Solo una cosa che non è ancora chiara. Se NSString* s = [[NSString alloc] initWithString:@"Hello World"];restituisce un oggetto rilasciato automaticamente (così come lo scrivi) perché devo fare un return [s autorelease];e impostarlo di nuovo "autorelease" e non solo return s?
znq

3
@Stefan: [[NSString alloc] initWithString:@"Hello World"]NON restituirà un oggetto rilasciato automaticamente. Ogni volta che allocviene chiamato, il conteggio dei riferimenti è impostato su 1 ed è responsabilità di quel codice assicurarsi che venga rilasciato. La [NSString stringWithString:]chiamata, d'altra parte, non restituisce un oggetto autoreleased.
Matt Dillard

6
Curiosità: poiché la risposta utilizza @ "" e NSString, le stringhe sono costanti in tutto e, quindi, il conteggio di conservazione assoluto sarà sia costante che del tutto irrilevante .... non rende la risposta sbagliata, in alcun modo, solo rafforza il fatto che i conteggi di conservazione assoluti non sono mai qualcosa di cui dovresti preoccuparti.
bbum

10

Se comprendi il processo di conservazione / rilascio, allora ci sono due regole d'oro che sono "duh" ovvie per i programmatori Cocoa affermati, ma sfortunatamente sono raramente spiegate chiaramente per i nuovi arrivati.

  1. Se una funzione che restituisce un oggetto ha alloc, createo copynel suo nome, l'oggetto è tuo. Devi chiamare [object release]quando hai finito. Oppure CFRelease(object), se è un oggetto Core-Foundation.

  2. Se NON ha una di queste parole nel suo nome, l'oggetto appartiene a qualcun altro. È necessario chiamare [object retain]se si desidera mantenere l'oggetto dopo la fine della funzione.

Saresti ben servito a seguire questa convenzione anche nelle funzioni che crei tu stesso.

(Nitpickers: Sì, purtroppo ci sono alcune chiamate API che sono eccezioni a queste regole ma sono rare).


11
Questo è incompleto e impreciso. Continuo a non capire perché le persone provano a ripetere le regole piuttosto che semplicemente indicare la documentazione pertinente: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc

4
Le regole della Core Foundation in particolare sono diverse da quelle del Cocoa; vedi developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc

1
Anch'io non sono d'accordo. Se una funzione restituisce qualcosa che non desidera possedere, dovrebbe rilasciarla automaticamente. È il chiamante del lavoro delle funzioni per conservarlo (se lo si desidera). Non dovrebbe avere NIENTE a che fare con il nome di qualsiasi metodo invocato. Questa è più codifica in stile C in cui la proprietà degli oggetti non è chiara.
Sam

1
Scusa! Penso di essere stato frettoloso nel voto negativo. Regole di gestione della memoria La tua risposta cita quasi il documento Apple.
Sam

8

Se stai scrivendo codice per il desktop e puoi scegliere come destinazione Mac OS X 10.5, dovresti almeno esaminare l'utilizzo della garbage collection Objective-C. Semplificherà davvero la maggior parte del tuo sviluppo, ecco perché Apple si è impegnata a crearlo in primo luogo e farlo funzionare bene.

Per quanto riguarda le regole di gestione della memoria quando non si utilizza GC:

  • Se si crea un nuovo oggetto usando +alloc/+allocWithZone:, +new, -copyo -mutableCopyoppure se -retainun oggetto, si sta assumendo la proprietà di esso e necessario assicurarsi che venga inviato-release .
  • Se si riceve un oggetto in qualsiasi altro modo, si è non è il proprietario di esso e dovrebbe non assicurarsi che venga inviato-release .
  • Se vuoi assicurarti che un oggetto sia inviato -release, puoi inviarlo tu stesso, oppure puoi inviare l'oggetto -autoreleasee il pool di rilascio automatico corrente lo invierà -release(una volta per ricevuto -autorelease) quando il pool viene svuotato.

In genere -autoreleaseviene utilizzato per garantire che gli oggetti vivano per la durata dell'evento corrente, ma vengono puliti in seguito, poiché è presente un pool di rilascio automatico che circonda l'elaborazione degli eventi di Cocoa. In Cocoa, è molto più comune restituire oggetti a un chiamante che sono rilasciati automaticamente piuttosto che restituire oggetti che il chiamante stesso deve rilasciare.


6

Objective-C utilizza il conteggio dei riferimenti , il che significa che ogni oggetto ha un conteggio dei riferimenti. Quando viene creato un oggetto, ha un conteggio dei riferimenti pari a "1". In parole semplici, quando si fa riferimento a un oggetto (cioè memorizzato da qualche parte), viene "trattenuto", il che significa che il conteggio dei riferimenti viene aumentato di uno. Quando un oggetto non è più necessario, viene "rilasciato", il che significa che il conteggio dei riferimenti viene ridotto di uno.

Quando il conteggio dei riferimenti di un oggetto è 0, l'oggetto viene liberato. Questo è il conteggio dei riferimenti di base.

Per alcune lingue, i riferimenti vengono automaticamente aumentati e diminuiti, ma l'obbiettivo-c non è una di quelle lingue. Pertanto, il programmatore è responsabile della conservazione e del rilascio.

Un modo tipico per scrivere un metodo è:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

Il problema di dover ricordare di rilasciare le risorse acquisite all'interno del codice è sia noioso che soggetto a errori. Objective-C introduce un altro concetto volto a rendere tutto più semplice: i pool di rilascio automatico. I pool di rilascio automatico sono oggetti speciali installati su ogni thread. Sono una classe abbastanza semplice, se cerchi NSAutoreleasePool.

Quando un oggetto riceve un messaggio di "rilascio automatico" inviato ad esso, l'oggetto cercherà tutti i pool di rilascio automatico che si trovano nello stack per questo thread corrente. Aggiungerà l'oggetto all'elenco come un oggetto a cui inviare un messaggio di "rilascio" in futuro, che in genere è quando il pool stesso viene rilasciato.

Prendendo il codice sopra, puoi riscriverlo per essere più breve e più facile da leggere dicendo:

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Poiché l'oggetto viene rilasciato automaticamente, non è più necessario chiamare esplicitamente "release" su di esso. Questo perché sappiamo che alcuni pool di rilascio automatico lo faranno per noi in seguito.

Si spera che questo aiuti. L'articolo di Wikipedia è abbastanza buono sul conteggio dei riferimenti. Ulteriori informazioni sui pool di rilascio automatico sono disponibili qui . Nota inoltre che se stai compilando per Mac OS X 10.5 e versioni successive, puoi dire a Xcode di compilare con garbage collection abilitata, permettendoti di ignorare completamente il mantenimento / rilascio / rilascio automatico.


2
Questo è semplicemente sbagliato. Non è necessario inviare alcuna versione di oggetto o rilascio automatico in nessuno degli esempi mostrati.
mmalc

6

Joshua (# 6591) - Il materiale di Garbage Collection in Mac OS X 10.5 sembra piuttosto interessante, ma non è disponibile per iPhone (o se vuoi che la tua app funzioni su versioni precedenti alla 10.5 di Mac OS X).

Inoltre, se stai scrivendo una libreria o qualcosa che potrebbe essere riutilizzato, l'utilizzo della modalità GC blocca chiunque utilizzi il codice anche utilizzando la modalità GC, quindi a quanto ho capito, chiunque cerchi di scrivere codice ampiamente riutilizzabile tende a gestire memoria manualmente.


2
È perfettamente possibile scrivere un framework ibrido che supporti sia il GC che il conteggio dei riferimenti.
mmalc

6

Come sempre, quando le persone iniziano a cercare di riformulare il materiale di riferimento, quasi sempre sbagliano o forniscono una descrizione incompleta.

Apple fornisce una descrizione completa del sistema di gestione della memoria di Cocoa nella Guida alla programmazione della gestione della memoria per Cocoa , alla fine della quale c'è un breve ma accurato riepilogo delle regole di gestione della memoria .




2
In realtà questo è un riassunto di una sola pagina molto migliore: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau

6

Non aggiungerò allo specifico di conservazione / rilascio a parte il fatto che potresti pensare di lasciare $ 50 e ottenere il libro di Hillegass, ma suggerirei caldamente di utilizzare gli strumenti di strumenti molto presto nello sviluppo della tua applicazione (anche il tuo il primo!). Per farlo, esegui-> Inizia con gli strumenti per le prestazioni. Inizierei con Leaks, che è solo uno dei tanti strumenti disponibili, ma ti aiuterà a mostrarti quando ti sei dimenticato di rilasciare. È quasi scoraggiante quante informazioni ti verranno presentate. Ma dai un'occhiata a questo tutorial per alzarti e andare veloce:
TUTORIAL CACAO: FISSARE LE PERDITE DI MEMORIA CON GLI STRUMENTI

In realtà, cercare di forzare le perdite potrebbe essere un modo migliore per imparare a prevenirle! In bocca al lupo ;)


5

Matt Dillard ha scritto :

return [[s autorelease] release];

Autorelease non trattiene l'oggetto. Autorelease lo mette semplicemente in coda per essere rilasciato in seguito. Non vuoi avere una dichiarazione di rilascio lì.




4

La risposta di NilObject è un buon inizio. Ecco alcune informazioni supplementari relative alla gestione manuale della memoria ( richiesta su iPhone ).

Se personalmente alloc/initun oggetto, viene fornito con un conteggio dei riferimenti pari a 1. Sei responsabile della pulizia dopo che non è più necessario, chiamando [foo release]o[foo autorelease] . release lo pulisce immediatamente, mentre autorelease aggiunge l'oggetto al pool di autorelease, che lo rilascerà automaticamente in un secondo momento.

autorelease è principalmente per quando hai un metodo che deve restituire l'oggetto in questione ( quindi non puoi rilasciarlo manualmente, altrimenti restituirai un oggetto nullo ) ma non vuoi nemmeno trattenerlo .

Se acquisisci un oggetto in cui non hai chiamato alloc / init per ottenerlo, ad esempio:

foo = [NSString stringWithString:@"hello"];

ma se vuoi aggrapparti a questo oggetto, devi chiamare [foo keep]. In caso contrario, è possibile che otterrà autoreleasede sarete aggrappato ad un punto di riferimento a zero (come sarebbe nel precedente stringWithStringesempio ). Quando non ne hai più bisogno, chiama [foo release].


2

Le risposte di cui sopra danno chiare riaffermazioni di ciò che dice la documentazione; il problema in cui si imbatte la maggior parte delle nuove persone sono i casi privi di documenti. Per esempio:

  • Rilascio automatico : i documenti dicono che attiverà una versione "in futuro". QUANDO?! Fondamentalmente, puoi contare sull'oggetto in giro fino a quando non esci dal codice nel ciclo degli eventi di sistema. Il sistema PU rilasciare l'oggetto in qualsiasi momento dopo il ciclo dell'evento corrente. (Penso che Matt l'abbia detto prima.)

  • Stringhe statiche : NSString *foo = @"bar";- devi mantenerlo o rilasciarlo? No. Che ne dici

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • La regola di creazione : se l'hai creata, ne sei il proprietario e ci si aspetta che la rilasci.

In generale, il modo in cui i nuovi programmatori Cocoa si incasinano è non capire quali routine restituiscono un oggetto con estensione retainCount > 0.

Ecco uno snippet di Regole molto semplici per la gestione della memoria in Cocoa :

Regole di conteggio conservazione

  • All'interno di un dato blocco, l'uso di -copy, -alloc e -retain dovrebbe essere uguale all'uso di -release e -autorelease.
  • Gli oggetti creati utilizzando costruttori di convenienza (ad esempio stringWithString di NSString) sono considerati rilasciati automaticamente.
  • Implementa un metodo -dealloc per rilasciare le variabili di istanza che possiedi

Il primo punto dice: se hai chiamato alloc(o new fooCopy), devi chiamare il rilascio su quell'oggetto.

Il secondo punto dice: se usi un comodo costruttore e hai bisogno che l'oggetto rimanga sospeso (come con un'immagine da disegnare in seguito), devi conservarlo (e poi rilasciarlo).

Il terzo dovrebbe essere autoesplicativo.


"Rilascio automatico: i documenti dicono che attiverà un rilascio" in futuro. "QUANDO ?!" La documentazione è chiara su questo punto: "autorelease significa semplicemente" inviare un messaggio di rilascio in un secondo momento "(per qualche definizione di più tardi, vedere" Pool di rilascio automatico ")." Esattamente quando dipende dallo stack del pool di rilascio automatico ...
mmalc

... "Il sistema PU rilasciare l'oggetto in qualsiasi momento dopo il ciclo di eventi corrente." Questo rende il suono del sistema un po 'meno deterministico di quanto non sia ...
mmalc

... NSString pippo = [self getBar]; // ancora non c'è bisogno di conservare o rilasciare Questo è sbagliato. Chiunque invoca getBar non conosce i dettagli di implementazione, quindi * dovrebbe mantenerlo / rilasciarlo (tipicamente tramite funzioni di accesso) se vuole usarlo al di fuori dell'ambito corrente.
mmalc

L'articolo "Regole molto semplici per la gestione della memoria in Cocoa" è per molti aspetti obsoleto, in particolare "Gli oggetti creati utilizzando costruttori di convenienza (ad esempio stringWithString di NSString) sono considerati rilasciati automaticamente". non è giusto - semplicemente "non è di proprietà del destinatario".
mmalc


0

Come molte persone hanno già detto, l' intro di Apple alla gestione della memoria è di gran lunga il miglior punto di partenza.

Un collegamento utile che non ho ancora visto menzionato è Practical Memory Management . Lo troverai nel mezzo dei documenti di Apple se li leggi, ma vale la pena collegarli direttamente. È un brillante riassunto esecutivo delle regole di gestione della memoria con esempi ed errori comuni (fondamentalmente quali altre risposte qui stanno cercando di spiegare, ma non altrettanto).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.