¿Por qué a los delegados de Objective-C generalmente se les asigna la propiedad en lugar de retener?

176

Estoy navegando por el maravilloso blog mantenido por Scott Stevenson, y estoy tratando de entender un concepto fundamental del Objetivo-C de asignar a los delegados la propiedad 'asignar' frente a 'retener'. Tenga en cuenta que ambos son iguales en un entorno de recolección de basura. Me preocupa principalmente un entorno no basado en GC (por ejemplo: iPhone).

Directamente desde el blog de Scott:

"La palabra clave de asignación generará un setter que asigna el valor a la variable de instancia directamente, en lugar de copiarlo o retenerlo. Esto es mejor para tipos primitivos como NSInteger y CGFloat, u objetos que no son directamente suyos, como los delegados".

¿Qué significa que no posee directamente el objeto delegado? Normalmente retengo a mis delegados, porque si no quiero que se vayan al abismo, retener se encargará de eso por mí. Por lo general, abstraigo UITableViewController lejos de su respectiva fuente de datos y delego también. También conservo ese objeto en particular. Quiero asegurarme de que nunca desaparezca para que mi UITableView siempre tenga su delegado.

¿Alguien más puede explicar dónde / por qué estoy equivocado, para poder entender este paradigma común en la programación de Objective-C 2.0 de usar la propiedad de asignación en los delegados en lugar de retener?

¡Gracias!

Plumenator
fuente
Etiquetado con "delegados" y sin "iphone".
Quinn Taylor
¿Por qué se asigna el delegado en lugar de copiar (como NSString?)
OMGPOP

Respuestas:

175

La razón por la que evita retener delegados es que debe evitar un ciclo de retención:

A crea B A se establece como delegado de B ... A es liberado por su propietario

Si B hubiera retenido a A, A no se liberaría, ya que B es dueño de A, por lo tanto, el dealloc de A nunca se llamaría, lo que haría que A y B fugas.

No debe preocuparse de que A desaparezca porque posee B y, por lo tanto, se deshace de él en dealloc.

Andrew Pouliot
fuente
No estoy de acuerdo, Mike. Acabo de encontrar un problema donde un modal tiene un delegado que descarta el modal. Pero cuando hago una advertencia de memoria en el modal, libera al delegado. Luego, cuando voy a descartar mi modal, el delegado es nulo. Choque.
Paul Shapiro
Ok, no estoy de acuerdo, pero tienes razón en que es un defecto de diseño. Descubrí que estaba pasando mi llamada de despido a través de una clase infantil del verdadero descartador. Esa clase de niño se liberó y no pudo pasar al delegado del contenedor modal para despedir. Lo cambié para pasar un puntero al delegado final y ese no se liberó en la advertencia de memoria y todo está bien.
Paul Shapiro
2
Su código nunca debe escribirse de tal manera que un delegado nulo haga que se bloquee. Solo el objeto propietario debe tener una referencia propietaria. Cuando se desasigna, debe establecer los delegados de los objetos de propiedad en cero antes de liberarlos. Entonces, cualquier mensaje enviado al delegado nulo simplemente se ignora. Sin embargo, pasar un objeto nulo en un mensaje puede bloquearse. Solo asegúrese de no tratar con los delegados de esa manera.
David Gish
Espera, ¿no es eso lo que weakhace? La pregunta es ¿por qué usar en assignlugar de weak?
wcochran
3
@wcochran: No, esta pregunta es por qué usar en assignlugar de retain. La pregunta es más antigua que ARC; weaky strong(este último es sinónimo de retain) no existió hasta que se introdujo ARC. Debe hacer su pregunta sobre weakvs. por assignseparado.
Peter Hosey
44

Porque el objeto que envía los mensajes del delegado no es el propietario del delegado.

Muchas veces, es al revés, como cuando un controlador se establece a sí mismo como el delegado de una vista o ventana: el controlador posee la vista / ventana, por lo que si la vista / ventana fuera su delegado, ambos objetos se poseerían entre sí. Esto, por supuesto, es un ciclo de retención, similar a una fuga con la misma consecuencia (los objetos que deberían estar muertos permanecen vivos).

Otras veces, los objetos son iguales: ninguno de los dos posee el otro, probablemente porque ambos son propiedad del mismo tercer objeto.

De cualquier manera, el objeto con el delegado no debe retener a su delegado.

(Hay al menos una excepción, por cierto. No recuerdo qué fue, y no creo que haya una buena razón para ello).


Anexo (agregado 2012-05-19): bajo ARC, debe usar en weaklugar de assign. Las referencias débiles se configuran nilautomáticamente cuando el objeto muere, eliminando la posibilidad de que el objeto delegante termine enviando mensajes al delegado muerto.

Si se mantiene alejado de ARC por alguna razón, al menos cambie las assignpropiedades que apuntan a objetos unsafe_unretained, lo que hace explícito que se trata de una referencia no retenida pero no cero a un objeto.

assign sigue siendo apropiado para valores que no sean de objeto tanto en ARC como en MRC.

Peter Hosey
fuente
13
NSURLConnectionretiene a su delegado.
Sí, use weak, pero eso no responde a la pregunta original: ¿Por qué Apple usa en assignlugar de weak?
wcochran
@wcochran: La pregunta original era "¿por qué se otorgan propiedades delegadas en assignlugar de retenerlas ? weakno existía cuando se le preguntó. Su pregunta es diferente, que debe hacer por separado. Estaré encantado de responderlo.
Peter Hosey
@wcochran y Peter, ¿se ha hecho esa pregunta en otro lugar?
Sr. Rogers
17

Tenga en cuenta que cuando tiene un delegado asignado, hace que sea muy importante establecer ese valor de delegado en nulo siempre que el objeto se desasigne, por lo que un objeto siempre debe tener cuidado de anular las referencias de delegado en dealloc si no lo ha hecho. hecho así en otra parte.

Kendall Helmstetter Gelner
fuente
"Tenga en cuenta que cuando tiene un delegado asignado, hace que sea muy importante establecer siempre ese valor de delegado en nulo cada vez que el objeto va a ser desasignado" ¿Por qué?
Peter Hosey
2
Debido a que cualquier referencia dejada establecida, será inválida después de que un objeto sea desasignado (apuntando a la memoria que ya no está asignada al tipo de objeto esperado) y, por lo tanto, provocará un bloqueo si intenta usarlo. Una señal de esto en el depurador es cuando el depurador afirma que alguna variable tiene un tipo que parece totalmente incorrecto de lo que realmente se declara como variable.
Kendall Helmstetter Gelner
1
Esto solo es necesario si el objeto del que es delegado está siendo retenido por otra fuente, como un temporizador u otra devolución de llamada asincrónica. De lo contrario, se desasignará después de liberarlo y no intentará llamar a métodos delegados.
Andrew Pouliot
@ Andrew: Eso es cierto, pero si siempre haces una práctica excluir a los delegados, entonces no olvidarás cuándo es importante, o si accidentalmente retengas un objeto retenido y se queda alrededor de todos modos. Si no delega al delegado, el resultado es solo una fuga, en lugar de una fuga seguida de un bloqueo.
Kendall Helmstetter Gelner
1

Una de las razones detrás de eso es para evitar retener ciclos. Solo para evitar el escenario en el que ambos objetos A y B se refieren entre sí y ninguno de ellos se libera de la memoria.

Asignar de forma aguda es mejor para tipos primitivos como NSInteger y CGFloat, u objetos que no son de su propiedad, como los delegados.

Puneet Sharma
fuente
Eso es bastante copiado de la cita del OP y la respuesta aceptada respectivamente, ¿no?
dakab