Perché @autoreleasepool è ancora necessario con ARC?


191

Per la maggior parte con ARC (Automatic Reference Counting), non è necessario pensare alla gestione della memoria con gli oggetti Objective-C. Non è più possibile creare NSAutoreleasePools, tuttavia esiste una nuova sintassi:

@autoreleasepool {
    
}

La mia domanda è: perché mai dovrei averne bisogno quando non dovrei rilasciare / rilasciare manualmente?


EDIT: Per riassumere ciò che ho ottenuto da tutte le risposte e commenti in modo succinto:

Nuova sintassi:

@autoreleasepool { … } è la nuova sintassi per

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[pool drain];

Ma ancora più importante:

  • ARC utilizza autoreleasecosì come release.
  • Per farlo è necessario un pool di rilascio automatico.
  • ARC non crea il pool di versioni automatiche per te. Però:
    • Il thread principale di ogni app Cocoa contiene già un pool di rilascio automatico.
  • Ci sono due occasioni in cui potresti voler usare @autoreleasepool:
    1. Quando ci si trova in un thread secondario e non esiste un pool di rilascio automatico, è necessario crearne uno proprio per evitare perdite, ad esempio myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. Quando desideri creare un pool più locale, come ha mostrato @mattjgalloway nella sua risposta.

1
C'è anche una terza occasione: quando sviluppi qualcosa che non è correlato a UIKit o NSFoundation. Qualcosa che utilizza gli strumenti della riga di comando o giù di

Risposte:


215

ARC non elimina ritardi, rilasci e rilasci automatici, aggiunge solo quelli richiesti per te. Quindi ci sono ancora chiamate da conservare, ci sono ancora chiamate da rilasciare, ci sono ancora chiamate a rilascio automatico e ci sono ancora pool di rilascio automatico.

Una delle altre modifiche apportate con il nuovo compilatore Clang 3.0 e ARC è che sono state sostituite NSAutoReleasePoolcon la @autoreleasepooldirettiva del compilatore. NSAutoReleasePoolè sempre stato un po 'un "oggetto" speciale e lo hanno fatto in modo che la sintassi dell'uso di uno non fosse confusa con un oggetto, in modo che fosse generalmente un po' più semplice.

Quindi, in sostanza, è necessario @autoreleasepoolperché ci sono ancora pool di rilascio automatico di cui preoccuparsi. Non devi preoccuparti di aggiungere autoreleasechiamate.

Un esempio di utilizzo di un pool di versioni automatiche:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

Un esempio enormemente inventato, certo, ma se non avessi l' @autoreleasepoolinterno del fortelaio esterno, rilasci successivamente 100000000 oggetti anziché 10000 ogni volta attorno al fortelaio esterno .

Aggiornamento: vedi anche questa risposta - https://stackoverflow.com/a/7950636/1068248 - perché @autoreleasepoolnon ha nulla a che fare con ARC.

Aggiornamento: ho dato un'occhiata all'interno di quello che sta succedendo qui e l' ho scritto sul mio blog . Se dai un'occhiata lì, vedrai esattamente cosa sta facendo ARC e come il nuovo stile @autoreleasepoole come introduce un ambito viene utilizzato dal compilatore per inferire le informazioni su ciò che sono necessari per conservare, rilasciare e rilasciare automaticamente.


11
Non elimina i ritardi. Li aggiunge per te. Il conteggio dei riferimenti è ancora in corso, è solo automatico. Da qui il conteggio di riferimento automatico :-D.
Mattjgalloway,

6
Quindi perché non aggiunge anche a @autoreleasepoolme? Se non controllo ciò che viene rilasciato o rilasciato automaticamente (ARC lo fa per me), come devo sapere quando impostare un pool di rilascio automatico?
MK12,

5
Ma hai il controllo su dove si fermano i pool di rilascio automatico. Ce n'è uno che avvolge l'intera app per impostazione predefinita, ma potresti volerne di più.
Mattjgalloway,

5
Buona domanda. Devi solo "sapere". Pensa di aggiungerne uno simile al perché uno potrebbe, in un linguaggio GC, aggiungere un suggerimento a un garbage collector per andare avanti ed eseguire un ciclo di raccolta ora. Forse sai che ci sono un sacco di oggetti pronti per essere cancellati, hai un loop che alloca un mucchio di oggetti temp, quindi "sai" (o Instruments potrebbe dirti :) che l'aggiunta di un pool di rilasci attorno al loop sarebbe un buona idea.
Graham Perks,

6
L'esempio del loop funziona perfettamente senza il rilascio automatico: ogni oggetto viene deallocato quando la variabile esce dall'ambito. L'esecuzione del codice senza il rilascio automatico richiede una quantità costante di memoria e mostra i puntatori che vengono riutilizzati e l'inserimento di un punto di interruzione sul dealloc di un oggetto mostra che viene chiamato una volta ogni volta attraverso il ciclo, quando viene chiamato objc_storeStrong. Forse OSX fa qualcosa di stupido qui, ma autoreleasepool è completamente inutile su iOS.
Glenn Maynard,

16

@autoreleasepoolnon rilascia automaticamente nulla. Crea un pool di rilascio automatico, in modo che quando viene raggiunta la fine del blocco, tutti gli oggetti che sono stati rilasciati automaticamente da ARC mentre il blocco era attivo verranno inviati messaggi di rilascio. La Guida alla programmazione avanzata della gestione della memoria di Apple spiega così:

Alla fine del blocco pool di rilascio automatico, agli oggetti che hanno ricevuto un messaggio di rilascio automatico all'interno del blocco viene inviato un messaggio di rilascio: un oggetto riceve un messaggio di rilascio ogni volta che viene inviato un messaggio di rilascio automatico all'interno del blocco.


1
Non necessariamente. L'oggetto riceverà un releasemessaggio ma se il conteggio di mantenimento è> 1 l'oggetto NON verrà deallocato.
andybons

@andybons: aggiornato; Grazie. È un cambiamento rispetto al comportamento pre-ARC?
uscita il

Questo non è corretto Gli oggetti rilasciati da ARC verranno inviati messaggi di rilascio non appena rilasciati da ARC, con o senza un pool di rilascio automatico.
Glenn Maynard,

7

Le persone spesso fraintendono ARC per un qualche tipo di raccolta dei rifiuti o simili. La verità è che, dopo un po 'di tempo, le persone di Apple (grazie ai progetti llvm e clang) hanno capito che l'amministrazione della memoria di Objective-C (tutte le retainse releases, ecc.) Può essere completamente automatizzata al momento della compilazione . Questo è, solo leggendo il codice, anche prima che venga eseguito! :)

Per fare ciò c'è solo una condizione: DEVIAMO seguire il regole , altrimenti il ​​compilatore non sarebbe in grado di automatizzare il processo in fase di compilazione. Quindi, al fine di garantire che noi non rompere le regole, non c'è permesso di scrivere in modo esplicito release, retainecc Quelle chiamate vengono automaticamente iniettato nel nostro codice dal compilatore. Quindi internamente abbiamo ancora autoreleases, retain, release, ecc E 'solo che non c'è bisogno di scrivere più di loro.

La A di ARC è automatica in fase di compilazione, il che è molto meglio che in fase di esecuzione come la garbage collection.

Noi abbiamo ancora @autoreleasepool{...} perché averlo non infrange nessuna delle regole, siamo liberi di creare / svuotare il nostro pool ogni volta che ne abbiamo bisogno :).


1
ARC sta contando il GC di riferimento, non il GC mark-and-sweep come si ottiene in JavaScript e Java, ma è sicuramente la garbage collection. Questo non risponde alla domanda: "puoi" non risponde alla domanda "perché dovresti". Non dovresti.
Glenn Maynard,

3

È perché è ancora necessario fornire al compilatore suggerimenti su quando è sicuro che gli oggetti rilasciati automaticamente non rientrino nell'ambito.


Puoi darmi un esempio di quando avresti bisogno di farlo?
MK12,


Quindi prima di ARC, ad esempio, avevo un CVDisplayLink in esecuzione su un thread secondario per la mia app OpenGL, ma non creavo un pool di rilascio automatico nel suo runloop perché sapevo che non stavo rilasciando nulla di automatico (o usando librerie che lo facessero). Vuol dire che ora devo aggiungere @autoreleasepoolperché non so se ARC potrebbe decidere di autorizzare il rilascio di qualcosa?
MK12,

@ Mk12 - No. Avrai sempre un pool di rilascio automatico che viene svuotato ogni volta attorno al ciclo di esecuzione principale. Dovresti solo aggiungerne uno quando vuoi assicurarti che gli oggetti che sono stati rilasciati automaticamente vengano svuotati prima che altrimenti lo farebbero, ad esempio la prossima volta attorno al ciclo di esecuzione.
Mattjgalloway,

2
@DougW - Ho dato un'occhiata a ciò che il compilatore sta effettivamente facendo e ne ho fatto un blog qui - iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. Spero che spieghi cosa sta succedendo sia in fase di compilazione che in fase di esecuzione.
Mattjgalloway,

2

Citato da https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html :

Blocchi pool e thread di rilascio automatico

Ogni thread in un'applicazione Cocoa mantiene il proprio stack di blocchi pool con rilascio automatico. Se si sta scrivendo un programma solo per Foundation o se si stacca un thread, è necessario creare il proprio blocco pool di rilascio automatico.

Se l'applicazione o il thread sono di lunga durata e potenzialmente generano molti oggetti rilasciati automaticamente, è necessario utilizzare blocchi di pool di rilascio automatico (come AppKit e UIKit fanno sul thread principale); in caso contrario, gli oggetti rilasciati automaticamente si accumulano e la tua impronta di memoria aumenta. Se il thread disconnesso non effettua chiamate Cocoa, non è necessario utilizzare un blocco pool di rilascio automatico.

Nota: se si creano thread secondari utilizzando le API dei thread POSIX anziché NSThread, non è possibile utilizzare Cocoa a meno che Cocoa non sia in modalità multithreading. Il cacao entra nella modalità multithreading solo dopo aver staccato il suo primo oggetto NSThread. Per usare Cocoa su thread POSIX secondari, l'applicazione deve prima staccare almeno un oggetto NSThread, che può uscire immediatamente. È possibile verificare se Cocoa è in modalità multithreading con il metodo della classe NSThread isMultiThreaded.

...

Nel conteggio dei riferimenti automatico, o ARC, il sistema utilizza lo stesso sistema di conteggio dei riferimenti di MRR, ma inserisce i metodi di gestione della memoria appropriati richiesti al momento della compilazione. Si consiglia vivamente di utilizzare ARC per nuovi progetti. Se si utilizza ARC, in genere non è necessario comprendere l'implementazione sottostante descritta in questo documento, sebbene in alcune situazioni possa essere utile. Per ulteriori informazioni su ARC, vedere Transizione alle note di rilascio ARC.


2

I pool di rilascio automatico sono necessari per restituire oggetti appena creati da un metodo. Ad esempio, considera questo codice:

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

La stringa creata nel metodo avrà un conteggio di mantenimento di uno. Ora chi bilancerà quel conteggio con un rilascio?

Il metodo stesso? Non è possibile, deve restituire l'oggetto creato, quindi non deve rilasciarlo prima della restituzione.

Il chiamante del metodo? Il chiamante non si aspetta di recuperare un oggetto che deve essere rilasciato, il nome del metodo non implica che venga creato un nuovo oggetto, dice solo che un oggetto viene restituito e questo oggetto restituito potrebbe essere uno nuovo che richiede un rilascio, ma potrebbe be essere uno esistente che non lo fa. Ciò che il metodo restituisce può anche dipendere da uno stato interno, quindi il chiamante non può sapere se deve rilasciare quell'oggetto e non dovrebbe preoccuparsene.

Se il chiamante dovesse sempre rilasciare tutti gli oggetti restituiti per convenzione, ogni oggetto non appena creato dovrebbe sempre essere conservato prima di restituirlo da un metodo e dovrebbe essere rilasciato dal chiamante una volta uscito dall'ambito, a meno che viene restituito di nuovo. Ciò sarebbe altamente inefficiente in molti casi poiché si può evitare completamente di alterare i conteggi di conservazione in molti casi se il chiamante non rilascerà sempre l'oggetto restituito.

Ecco perché ci sono pool di rilascio automatico, quindi diventerà il primo metodo

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

La chiamata autoreleasea un oggetto lo aggiunge al pool di autorelease, ma cosa significa realmente aggiungere un oggetto al pool di autorelease? Bene, significa dire al tuo sistema " Voglio che tu rilasci quell'oggetto per me, ma in un secondo momento, non ora; ha un conteggio che deve essere bilanciato da un rilascio, altrimenti la memoria perderà ma non posso farlo da solo in questo momento, poiché ho bisogno che l'oggetto rimanga vivo oltre il mio attuale scopo e il mio chiamante non lo farà neanche per me, non ha alcuna conoscenza di ciò che deve essere fatto. Quindi aggiungilo al tuo pool e una volta ripulito piscina, pulisci anche il mio oggetto per me. "

Con ARC il compilatore decide per te quando conservare un oggetto, quando rilasciare un oggetto e quando aggiungerlo a un pool di autorelease ma richiede comunque la presenza di pool di autorelease per poter restituire oggetti appena creati dai metodi senza perdita di memoria. Apple ha appena apportato alcune ottimizzazioni al codice generato che a volte eliminerà i pool di rilascio automatico durante il runtime. Queste ottimizzazioni richiedono che sia il chiamante che il chiamante utilizzino ARC (ricordare che il mix di ARC e non-ARC è legale e anche supportato ufficialmente) e, in tal caso, il caso può essere conosciuto solo in fase di esecuzione.

Considera questo codice ARC:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

Il codice generato dal sistema può comportarsi come il seguente codice (ovvero la versione sicura che consente di mescolare liberamente il codice ARC e non ARC):

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(Si noti che il mantenimento / rilascio nel chiamante è solo un mantenimento di sicurezza difensivo, non è strettamente necessario, il codice sarebbe perfettamente corretto senza di esso)

Oppure può comportarsi come questo codice, nel caso in cui entrambi vengano rilevati per utilizzare ARC in fase di esecuzione:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

Come puoi vedere, Apple elimina il rilascio di atuorel, quindi anche il rilascio ritardato di oggetti quando il pool viene distrutto, così come il mantenimento della sicurezza. Per saperne di più su come è possibile e cosa sta realmente succedendo dietro le quinte, un'occhiata a questo post sul blog.

Ora alla domanda reale: perché si dovrebbe usare @autoreleasepool ?

Per la maggior parte degli sviluppatori, oggi è rimasto solo un motivo per utilizzare questo costrutto nel loro codice ed è quello di mantenere piccolo l'ingombro della memoria ove applicabile. Ad esempio, considera questo ciclo:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Supponiamo che ogni chiamata a tempObjectForDatapossa crearne una nuovaTempObject che viene restituito con il rilascio automatico. Il for-loop creerà un milione di questi oggetti temporanei che sono tutti raccolti nell'attuale autoreleasepool e solo una volta distrutto quel pool, anche tutti gli oggetti temporanei vengono distrutti. Fino a quando ciò non accadrà, avrai un milione di questi oggetti temporanei in memoria.

Se invece scrivi il codice in questo modo:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Quindi viene creato un nuovo pool ogni volta che viene eseguito il ciclo for e viene distrutto alla fine di ogni iterazione del ciclo. In questo modo, al massimo, un oggetto temporaneo rimane in memoria in qualsiasi momento, nonostante il ciclo venga eseguito un milione di volte.

In passato spesso dovevi gestire anche i autoreleasepools quando gestisci i thread (ad es. Usando NSThread) poiché solo il thread principale ha automaticamente un pool di rilascio automatico per un'app Cocoa / UIKit. Eppure questa è praticamente un'eredità oggi poiché oggi probabilmente non useresti i thread per cominciare. Utilizzeresti GCD DispatchQueueo NSOperationQueuequesti e entrambi gestiscono un pool di autorelease di livello superiore per te, creato prima di eseguire un blocco / attività e distrutto una volta terminato.


-4

Sembra esserci molta confusione su questo argomento (e almeno 80 persone che probabilmente ora sono confuse su questo argomento e pensano di dover spargere @autoreleasepool attorno al loro codice).

Se un progetto (comprese le sue dipendenze) utilizza esclusivamente ARC, allora @autoreleasepool non deve mai essere usato e non farà nulla di utile. ARC gestirà il rilascio degli oggetti al momento giusto. Per esempio:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

display:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

Ogni oggetto Test viene deallocato non appena il valore esce dall'ambito, senza attendere l'uscita da un pool di rilascio automatico. (La stessa cosa accade con l'esempio NSNumber; questo ci consente di osservare il dealloc.) ARC non utilizza il rilascio automatico.

La ragione per cui @autoreleasepool è ancora consentita è per progetti ARC e non ARC misti, che non sono ancora passati completamente ad ARC.

Se si chiama in un codice non ARC, esso può restituire un oggetto autoreleased. In tal caso, il ciclo precedente potrebbe perdere, poiché il pool di rilascio automatico corrente non verrà mai chiuso. È qui che vorresti mettere un @autoreleasepool attorno al blocco di codice.

Ma se hai effettuato completamente la transizione ARC, dimentica la risposta automatica.


4
Questa risposta è errata e va anche contro la documentazione ARC. le tue prove sono aneddotiche perché ti capita di usare un metodo di allocazione che il compilatore decide di non rilasciare automaticamente. Puoi facilmente vederlo non funzionare se crei un nuovo inizializzatore statico per la tua classe personalizzata. Creare questo inizializzazione e utilizzarlo nel vostro ciclo: + (Testing *) testing { return [Testing new] }. Quindi vedrai che dealloc non verrà chiamato fino a dopo. Questo problema viene risolto se si avvolge l'interno del loop in un @autoreleasepoolblocco.
Dima,

@Dima Provato su iOS10, dealloc viene chiamato immediatamente dopo aver stampato l'indirizzo dell'oggetto. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC,

@KudoCC - Anche io, e ho visto lo stesso comportamento che hai fatto tu. Ma, quando ho gettato [UIImage imageWithData]nell'equazione, poi, all'improvviso, ho iniziato a vedere il autoreleasecomportamento tradizionale , richiedendo @autoreleasepooldi mantenere la memoria di picco a un livello ragionevole.
Rob,

@Rob Non posso fare a meno di aggiungere il link .
KudoCC,
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.