Perché ai delegati Objective-C di solito viene assegnata la proprietà anziché trattenerla?


176

Sto navigando attraverso il meraviglioso blog gestito da Scott Stevenson e sto cercando di capire un concetto fondamentale di Objective-C di assegnare ai delegati la proprietà 'Assegna' vs 'Conserva'. Nota, entrambi sono uguali in un ambiente di raccolta rifiuti. Mi occupo principalmente di un ambiente non basato su GC (ad es. IPhone).

Direttamente dal blog di Scott:

"La parola chiave assegnata genererà un setter che assegna direttamente il valore alla variabile di istanza, piuttosto che copiarlo o conservarlo. Questo è meglio per tipi primitivi come NSInteger e CGFloat, o oggetti che non possiedi direttamente, come i delegati."

Cosa significa che non possiedi direttamente l'oggetto delegato? In genere trattengo i miei delegati, perché se non voglio che vadano via nell'abisso, si occuperà di me. Solitamente estraggo UITableViewController dal rispettivo DataSource e delegare anche. Conservo anche quel particolare oggetto. Voglio assicurarmi che non scompaia mai, quindi il mio UITableView ha sempre il suo delegato in giro.

Qualcuno può spiegare ulteriormente dove / perché mi sbaglio, così posso capire questo paradigma comune nella programmazione di Objective-C 2.0 di usare la proprietà assegnato sui delegati invece di conservare?

Grazie!


Contrassegnato con "delegati" e senza "iphone".
Quinn Taylor,

perché il delegato assegna invece di copiare (come NSString?)
OMGPOP

Risposte:


175

Il motivo per cui si evita di trattenere i delegati è che è necessario evitare un ciclo di mantenimento:

A crea B A si imposta come delegato di B ... A viene rilasciato dal suo proprietario

Se B avesse trattenuto A, A non verrebbe rilasciato, poiché B possiede A, quindi il dealloc di A non verrebbe mai chiamato, causando la fuga di A e B.

Non dovresti preoccuparti che A se ne vada perché possiede B e quindi se ne sbarazza in dealloc.


Non sono d'accordo, Mike. Ho appena riscontrato un problema in cui un modale ha un delegato che licenzia il modale. Ma quando faccio un avviso di memoria in modale, rilascia il delegato. Quindi quando vado a licenziare il mio modale, il delegato è zero. Crash.
Paul Shapiro,

Ok, quindi non sono d'accordo, ma hai ragione che è un difetto di progettazione. Ho scoperto che stavo trasmettendo la mia richiesta di licenziamento attraverso una classe figlio del vero licenziante. Quella classe figlio è stata rilasciata e non ha potuto passare al delegato container del modale per licenziarlo. L'ho cambiato per passare un puntatore al delegato finale e quello non è stato rilasciato in caso di avviso di memoria e tutto è a posto.
Paul Shapiro,

2
Il tuo codice non dovrebbe mai essere scritto in modo tale che un delegato nullo ne causi l'arresto anomalo. Solo l'oggetto proprietario dovrebbe avere un riferimento proprietario. Quando viene assegnato, deve impostare a zero i delegati degli oggetti di proprietà prima di rilasciarli. Quindi qualsiasi messaggio inviato al delegato zero viene semplicemente ignorato. Il passaggio di un oggetto nullo in un messaggio può tuttavia arrestarsi in modo anomalo. Assicurati solo di non trattare con i delegati in quel modo.
David Gish,

Aspetta, non è quello che weakfa? La domanda è: perché usare assigninvece di weak?
wcochran,

3
@wcochran: No, questa domanda è perché usare assigninvece di retain. La domanda è più antica di ARC; weake strong(quest'ultimo essendo sinonimo di retain) non esisteva fino all'introduzione di ARC. Dovresti porre la tua domanda su weakvs. assignseparatamente.
Peter Hosey,

44

Perché l'oggetto che invia i messaggi delegato non possiede il delegato.

Molte volte, è il contrario, come quando un controller si imposta come delegato di una vista o finestra: il controller possiede la vista / finestra, quindi se la vista / finestra possedesse il suo delegato, entrambi gli oggetti si sarebbero posseduti l'un l'altro. Questo, ovviamente, è un ciclo di mantenimento, simile a una perdita con la stessa conseguenza (gli oggetti che dovrebbero essere morti rimangono vivi).

Altre volte, gli oggetti sono peer: nessuno dei due possiede l'altro, probabilmente perché entrambi sono di proprietà dello stesso terzo oggetto.

In entrambi i casi, l'oggetto con il delegato non deve conservare il proprio delegato.

(A proposito, c'è almeno un'eccezione. Non ricordo di cosa si trattasse e non credo che ci fosse una buona ragione per farlo.)


Addendum (aggiunto il 19/05/2012): in ARC, è necessario utilizzare weakinvece di assign. I riferimenti deboli vengono impostati nilautomaticamente su quando l'oggetto muore, eliminando la possibilità che l'oggetto delegante finisca per inviare messaggi al delegato morto.

Se stai lontano da ARC per qualche motivo, modifica almeno le assignproprietà che puntano agli oggetti unsafe_unretained, il che rende esplicito che si tratta di un riferimento non mantenuto ma azzerante di un oggetto.

assign rimane appropriato per i valori non oggetto sia in ARC che in MRC.


13
NSURLConnectionmantiene il suo delegato.

Sì, usa weak, ma questo non risponde alla domanda originale: perché Apple utilizza assigninvece di weak?
wcochran,

@wcochran: la domanda originale era "perché vengono assegnate le proprietà dei delegati assignanziché trattenerle "; weaknon esisteva quando è stato chiesto. La tua domanda è diversa, che dovresti porre separatamente. Sarei felice di risponderti.
Peter Hosey,

@wcochran e Peter, questa domanda è stata posta da qualche altra parte?
Mr Rogers,

17

Si noti che quando si dispone di un delegato assegnato, è molto importante impostare sempre quel valore del delegato su zero ogni volta che l'oggetto verrà deallocato, quindi un oggetto dovrebbe sempre fare attenzione a azzerare i riferimenti delegati nel dealloc se non ha fatto altrove.


"Si noti che quando si dispone di un delegato assegnato, è molto importante impostare sempre quel valore del delegato su zero ogni volta che l'oggetto verrà deallocato" Perché?
Peter Hosey,

2
Poiché qualsiasi riferimento lasciato impostato, non sarà valido dopo che un oggetto è stato allocato (indicando la memoria non più allocata al tipo di oggetto previsto) e quindi causando un arresto anomalo se si tenta di utilizzarlo. Un segno di ciò nel debugger è quando il debugger afferma che alcune variabili hanno un tipo che sembra totalmente sbagliato rispetto a ciò che la variabile viene effettivamente dichiarata.
Kendall Helmstetter Gelner,

1
Ciò è necessario solo se l'oggetto di cui sei delegato viene mantenuto da un'altra fonte, come un timer o un altro callback asincrono. Altrimenti, verrà distribuito dopo il rilascio e non tenterà di chiamare i metodi delegati.
Andrew Pouliot,

@Andrew: È vero, ma se fai sempre pratica per azzerare i delegati, non dimenticherai quando è importante o se accidentalmente tieni troppo un oggetto trattenuto e rimane comunque in giro. Se si annulla il delegato, il risultato è solo una perdita, anziché una perdita seguita da un arresto anomalo.
Kendall Helmstetter Gelner,

1

Uno dei motivi alla base di ciò è quello di evitare cicli di mantenimento. Solo per evitare lo scenario in cui A e B fanno riferimento a entrambi gli oggetti e nessuno di essi viene rilasciato dalla memoria.

Assegnare acutamente è la cosa migliore per tipi primitivi come NSInteger e CGFloat, o oggetti che non possiedi direttamente, come i delegati.


È piuttosto copiato rispettivamente dalla citazione del PO e dalla risposta accettata, vero?
dakab,
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.