2010-10-20 11 views
5

sto cercando di scrivere questo nel modo più conciso possibile, ma non è facile descrivere - quindi grazie per la lettura =)Blocchi oggettivi C: esiste un modo per evitare che il "sé" venga mantenuto?

Sono lo sviluppatore principale di iPhone Open Source quadro Sparrow. Sparrow è modellato dopo la libreria Flash AS3, e quindi ha un sistema di eventi proprio come AS3. Attualmente, quel sistema funziona specificando selettori - ma mi piacerebbe espandere quel sistema consentendo l'uso di blocchi per gli ascoltatori di eventi. Tuttavia, sto inciampando su problemi di gestione della memoria.

Ti mostrerò un tipico caso d'uso di eventi, man mano che vengono gestiti.

// init-method of a display object, inheriting from 
// the base event dispatcher class 
- (id)init 
{ 
    if (self = [super init]) 
    { 
     // the method 'addEventListener...' is defined in the base class 
     [self addEventListener:@selector(onAddedToStage:) 
         atObject:self 
         forType:SP_EVENT_TYPE_ADDED_TO_STAGE]; 
    } 
    return self; 
} 

// the corresponding event listener 
- (void)onAddedToStage:(SPEvent *)event 
{ 
    [self startAnimations]; // call some method of self 
} 

Questo è abbastanza semplice: quando un oggetto viene aggiunto all'elenco di visualizzazione, riceve un evento. Attualmente, la classe base registra i listener di eventi in una matrice di oggetti NSInvocation. NSInvocation viene creato in modo tale che lo non mantenga il proprio target e gli argomenti non. (L'utente può farlo, ma nel 99% dei casi non è necessario).

Che questi oggetti non vengano conservati è stata una scelta consapevole: altrimenti, il codice precedente causerebbe un porro di memoria, anche se l'utente ha rimosso il listener di eventi nel metodo dealloc! Ecco perché:

- (id)init 
{ 
    if (self = [super init]) 
    { 
     // [self addEventListener: ...] would somehow cause: 
     [self retain]; // (A) 
    } 
    return self; 
} 

// the corresponding event listener 
- (void)dealloc 
{ 
    // [self removeEventListener...] would cause: 
    [self release]; // (B) 
    [super dealloc]; 
} 

A prima vista, che sembra bene: la conservano nel init metodo è abbinato da un rilascio nel metodo dealloc. Tuttavia, ciò non funziona, poiché il metodo dealloc non verrà mai chiamato, perché il conteggio dei fermi non raggiunge mai lo zero!

Come ho già detto, il metodo 'addEventListener ...', proprio per questo motivo, non conserva nulla nella sua versione predefinita. A causa del modo in cui gli eventi funzionano (vengono quasi sempre inviati da "sé" o oggetti figlio, che vengono comunque mantenuti), non è un problema.

Tuttavia, e ora veniamo alla parte centrale della domanda: non posso farlo con i blocchi. Guardate il blocco-variante di gestione degli eventi, come vorrei che avesse:

- (id)init 
{ 
    if (self = [super init]) 
    { 
     [self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event) 
     { 
      [self startAnimations]; 
     }]; 
    } 
    return self; 
} 

che sembra grande e sarebbe molto facile da usare. Tuttavia: quando l'utente chiama un metodo su 'self' o usa una variabile membro all'interno del blocco - che sarà, beh, quasi sempre - il blocco manterrà automaticamente 'self', e l'oggetto non sarà mai deallocato .

Ora, so che qualsiasi utente potrebbe ovviare a tale situazione, facendo un riferimento __block a sé, in questo modo:

__block id blockSelf = self; 
[self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event) 
{ 
    [blockSelf startAnimations]; 
}]; 

Ma, onestamente, sono sicuro che quasi tutti gli utenti non saprebbe farlo o dimentica di farlo. Un'API dovrebbe essere non solo facile da usare, ma anche difficile da utilizzare in modo errato e questo chiaramente viola tale principio. Gli utenti dell'API lo abuserebbero sicuramente.

Quello che mi infastidisce è che io conosco che 'auto' non deve essere conservato - funziona nella mia attuale implementazione senza conservarlo. Quindi, I voglio dire al blocco che non ha bisogno di conservare se stesso, la libreria, dovrebbe dire al blocco che, in modo che l'utente non debba pensarci.

Nella mia ricerca, non ho trovato il modo di farlo.E non riesco a pensare a un modo per cambiare la mia architettura per adattarla a quella limitazione di blocchi.

Qualcuno ha avuto un'idea di cosa avrei potuto fare al riguardo?
Anche se non lo hai, grazie per aver letto fino a qui - so che è stata una domanda prolissa ;-)

risposta

5

Ho discusso di questo argomento con il supporto Apple, e mi hanno detto cosa mi aspettavo: al momento, non c'è modo che mi permetta di dire al blocco che non dovrebbe mantenere se stesso. Tuttavia, hanno offerto due modi per aggirare il problema.

Il primo sarebbe passare auto come argomento al blocco piuttosto che una variabile di blocco. L'API diventerebbe:

[self addEventListenerForType:ADDED_TO_STAGE 
         block:^(id selfReference, SPEvent *event) 

l'API sarebbe quindi responsabile del passaggio nel (non mantenuto) sé. Gli sviluppatori dovrebbero comunque sentirsi dire che devono usare questo riferimento al posto di se stessi, ma almeno sarebbe facile da usare.

L'altra soluzione che Apple ha utilizzato in questo tipo di situazione è fornire un metodo separato da release/dealloc per arrestare l'ascoltatore. Un buon esempio di questo è il metodo "invalidate" di NSTimer. È stato creato a causa di un ciclo di memoria tra NSRunLoop e NSTimer (in base alla progettazione).

+1

Questo è un bel modo per farlo, ma, per la cronaca, l'aliasing 'self' con una variabile' __block' impedirà anche il mantenimento. –

1

Penso che il design sia fondamentalmente imperfetto, blocchi o niente blocchi. Stai aggiungendo oggetti come listener di eventi prima che siano completamente inizializzati. Allo stesso modo, l'oggetto potrebbe ancora ricevere eventi mentre è stato deallocato. L'aggiunta e la rimozione di listener di eventi devono essere disaccoppiati dall'assegnazione/deallocazione IMO.

Per quanto riguarda il tuo problema immediato, potresti non aver bisogno di preoccuparti. Stai riscontrando un problema perché stai implicitamente aggiungendo un riferimento a un oggetto a se stesso (indirettamente) facendo riferimento a esso nel blocco. Tenere presente che è improbabile che chiunque fornisca un blocco faccia riferimento all'oggetto che genera l'evento o le sue variabili di istanza perché non si troveranno nello scope in cui è definito il blocco. Se lo sono (in una sottoclasse, per esempio), non c'è nulla che tu possa fare se non documentare il problema.

Un modo per ridurre la probabilità di un problema è fornire l'oggetto che genera l'evento come una proprietà sul parametro SPEvent e utilizzarlo nel blocco anziché fare riferimento a se stesso. Dovrai farlo comunque perché non è necessario creare un blocco in un contesto in cui l'oggetto che genera l'evento è in ambito.

+0

Grazie mille per la risposta, Jeremy! Per quanto riguarda il design: IMO non c'è niente di sbagliato nella creazione di un listener di eventi nel metodo init, a patto che ascoltino eventi da "self" o oggetti figlio. E questo è il caso qui. Ma comunque: il problema è che verrà usato come citato sopra: nel metodo init di una sottoclasse. Pertanto, gli utenti avranno accesso a se stessi e causeranno il problema. L'oggetto SPEvent ha già quel parametro. Ho solo paura che gli utenti scrivano il codice come sopra - e se non è risolvibile all'interno della libreria, preferirei lasciare i blocchi. – PrimaryFeather

+0

Quindi è necessario documentare la perdita potenziale. Non c'è nulla che tu possa fare per impedirgli di riferirsi a variabili di auto o istanza di sé e il mantenimento di sé da parte del blocco è documentato da Apple. – JeremyP

+0

Che peccato :-(Comunque, grazie ancora per il tuo tempo! Onestamente, in quel caso, penso che non si possa consigliare l'uso di blocchi che devono essere conservati per qualche tempo su una piattaforma senza garbage collection. sono usati in NSArray, ecc., vanno bene, ma tutto il resto è troppo pericoloso IMHO. È semplicemente troppo facile da trascurare. - Non segnerò la domanda come risposta subito, forse un altro utente ha un'altra idea; spero che tu stia bene! – PrimaryFeather

Problemi correlati