Perché le visualizzazioni secondarie di un NSView non inviano un messaggio di rilascio al termine di un'applicazione Cocoa?

StackOverflow https://stackoverflow.com/questions/1052304

Domanda

La versione breve:

  1. Perché le visualizzazioni secondarie degli oggetti NSView non inviano un messaggio release quando termina un'applicazione Cocoa?
  2. C'è un modo per ignorare questo comportamento?

Un esempio:
La classe MyView mostrata di seguito non è altro che una sottoclasse NSView che riporta alla console quando viene creata e distrutta. L'ho provato e l'ho trovato per funzionare correttamente. Tuttavia, quando lo uso come mostrato nello snippet di codice successivo dal mio delegato dell'applicazione, vedo qualcosa di inaspettato (vedi l'output di esempio).

// MyView:

@interface MyView : NSView { }
@end

@implementation MyView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) == nil) { return nil; }
    NSLog(@"init %@", self);
    return self;
}

- (void)dealloc
{
    NSLog(@"dealloc %@", self);
    [super dealloc];
}

@end

// Application delegate:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSLog(@"begin");

    parentView = [[MyView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];

    MyView * myView = [[MyView alloc] initWithFrame:NSMakeRect(10, 10, 80, 80)];
    [parentView addSubview:myView];
    [myView release];

    NSLog(@"run");
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
    NSLog(@"quit");
    [parentView release];
    NSLog(@"end.");
}

Questa applicazione produce il seguente output:

  

iniziare
  init < MyView: 0x10013f840 >
  init Trace_Init() MyView: 0x10261b620 Trace_Dealloc()
  eseguire
  uscire
  dealloc init MyView: 0x10013f840 dealloc
  fine.

Il problema:
Posso vedere chiaramente che il primo oggetto vista viene rilasciato quando l'applicazione si chiude e sono certo (testato e verificato) che atexit() gli oggetti rilasciano automaticamente le loro visualizzazioni secondarie quando vengono rilasciati. Tuttavia, sembra che durante la chiusura dell'applicazione tali visualizzazioni non vengano rilasciate.

La versione lunga: (AKA perché mai qualcuno dovrebbe interessarsene? :)
Vorrei iniziare dicendo che ho familiarità con il modo in cui la memoria viene liberata da un'applicazione in esecuzione quando si chiude. So che le mie anteprime verranno eliminate correttamente, anche se non vengono mai inviate un messaggio <=>, quindi non sono preoccupato che si tratti di una perdita. In effetti, sono abbastanza sicuro (ma non certo al 100%) che la risposta alla mia domanda n. 1 è: & Quot; Perché il rilascio di sottoview non è necessario quando l'applicazione sta per terminare. & Quot;

Uso un semplice codice a mano per eseguire il tracciamento della memoria mentre la mia applicazione è in esecuzione in modalità debug. Richiamo un metodo <=> e <=> nei metodi <=> e <=> di tutte le mie classi personalizzate e utilizzo la funzione <=> per segnalare eventuali oggetti non rilasciati dopo la parte Cocoa del mio l'applicazione è terminata. Trovo che questo sia molto più semplice rispetto all'esecuzione dello strumento di prestazioni della perdita di memoria di Apple su base regolare. Se causo una perdita di memoria durante l'esecuzione, lo saprò non appena l'applicazione verrà chiusa.

Tuttavia, la mancanza di una <=> chiamata durante la chiusura indica che una qualsiasi delle mie sottoclassi personalizzate <=> utilizzate come visualizzazioni secondarie vengono visualizzate come perdite di memoria quando chiudo l'applicazione. Quindi il motivo della mia domanda n. 2. Vorrei che Cocoa rilasciasse tutto durante la terminazione in modo che il mio tracciamento della memoria sia in grado di riassumere correttamente. Naturalmente, vorrei solo ignorare il comportamento predefinito in modalità debug. La mia app rilasciata non ha alcun codice di tracciamento della memoria abilitato e dovrebbe essere in grado di chiudersi in modo efficiente come di consueto.

Questo è tutto! (phew) Se sei arrivato così lontano, grazie per aver dedicato del tempo a leggere tutto.

È stato utile?

Soluzione

L'ho capito. La soluzione è stata quella di creare e rilasciare il mio NSAutoreleasePool all'interno del metodo applicationWillTerminate:.

Dettagli:
Nelle viscere del metodo NSView dealloc, vengono fatti tutti i tipi di cose per rimuovere la vista e tutte le sue sottoview dalla catena di rispondenti, impostare la vista chiave successiva, inviare messaggi delegati, ecc. questo codice, a ogni sottoview viene inviato un retain messaggio e successivamente inviato un autorelease messaggio. (In realtà, ogni sottoview viene conservata e rilasciata automaticamente due volte - vedere i dettagli di seguito). Questo è normale, ma ecco il kicker: quando le sottoview ricevono un release messaggio, vengono aggiunte a qualunque init sia attivo in quel momento e vengono mantenute in giro fino a quando quel particolare pool non si spegne scopo. In caso di chiusura dell'applicazione, il pool a cui vengono aggiunti è quello creato automaticamente durante ogni iterazione del ciclo di eventi principale dell'applicazione, e questo pool non viene mai inviato un messaggio MyView perché l'applicazione sta per chiudere!

Risultati sperimentali:
Ho aggiunto un sacco di messaggi di log ai metodi {, }, <=> e <=> per <=>, che hanno tutti un codice simile a questo:

NSLog(@"[%@ retain]:  count = %d", [self name], [self retainCount]+1);
return [super retain];

Ho anche registrato <=> <=> il codice per <=> in modo da poter vedere quando accade la magia.

Usando questi messaggi di log, ecco cosa succede ai miei <=> obbiettivi:

begin  
[parent init]:        count = 1
[subview init]:        count = 1
[subview retain]:      count = 2
[subview release]:     count = 1
run
quit
[parent release]:     count = 0
[parent dealloc]
{
    [subview retain]:      count = 2
    [subview autorelease]: count = 2
    [subview retain]:      count = 3
    [subview autorelease]: count = 3
    [subview release]:     count = 2
}
end.

Ora, quando uso il seguente codice in <=>

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
    NSLog(@"quit");
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    [parentView release];
    [pool release];
    NSLog(@"end.");
}

Il risultato è il seguente:

begin  
[parent init]:        count = 1
[subview init]:        count = 1
[subview retain]:      count = 2
[subview release]:     count = 1
run
quit
[parent release]:     count = 0
[parent dealloc]
{
    [subview retain]:      count = 2
    [subview autorelease]: count = 2
    [subview retain]:      count = 3
    [subview autorelease]: count = 3
    [subview release]:     count = 2
}
[subview release]:     count = 1
[subview release]:     count = 0
[subview dealloc]
{
}
end.

E puoi vedere chiaramente i due <=> messaggi inviati alla sottoview dal <=> mentre scarica.

Riferimenti:
NSView.m di GNUStep
Pool di autorelease dalla documentazione per sviluppatori di Apple

Altri suggerimenti

Non sono solo visualizzazioni. È tutto. Non penso nemmeno che l'oggetto NSApplication si rilasci da solo.

  

In effetti, sono abbastanza sicuro (ma non certo al 100%) che la risposta alla mia domanda n. 1 è: " Perché non è necessario rilasciare le subview quando l'applicazione sta per terminare. " ;

Lo credo anch'io.

Se vuoi che il grafico degli oggetti personalizzati venga rilasciato all'uscita, chiedi alla tua app di possederlo e rilascia gli altri oggetti di livello superiore in applicationWillTerminate :. Finché gestisci correttamente tutte le tue proprietà e rilasci ogni singolo oggetto personalizzato di primo livello da quel metodo, tutti i tuoi oggetti personalizzati, comprese le viste, moriranno.

Nota: non ho provato a mescolarlo con Core Data. Potrebbe essere o meno possibile farlo agli oggetti gestiti. Non ho alcuna esperienza diretta con quello.

Nel codice che hai presentato stai aggiungendo la sottoview a un ivar chiamato 'view'. È quello che hai fatto davvero o è solo dalla copia del codice alla domanda?

Lo chiedo perché se creo un IBOutlet nella visualizzazione del contenuto della finestra principale ed eseguo il codice, fa quello che dici. Ma se aggiungo myView local var a parentView, allora deallocate:

begin
init <MyView: 0x174460>
init <MyView: 0x174770>
run
quit
dealloc <MyView: 0x174460>
end
dealloc <MyView: 0x174770> 

Inoltre, sembra che le viste secondarie vengano rilasciate automaticamente (l'aggiunta di un messaggio di registro alla versione automatica lo dimostra).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top