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 autorelease
a 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 tempObjectForData
possa 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 DispatchQueue
o NSOperationQueue
questi e entrambi gestiscono un pool di autorelease di livello superiore per te, creato prima di eseguire un blocco / attività e distrutto una volta terminato.