2010-10-31 7 views
17

Estoy tratando de averiguar cuál es la práctica recomendada para la siguiente situación. Ciertos objetos, como CLLocationManager o MKReverseGeocoder, envían sus resultados de forma asíncrona a un método de devolución de llamada delegado. ¿Está bien liberar esa instancia CLLocationManager o MKReverseGeocoder (o la clase que sea) en el método de devolución de llamada? El punto es que ya no necesita ese objeto, por lo que le pide que deje de enviar actualizaciones, establezca su delegado en cero y libere el objeto.Liberación de un objeto delegado en su método de devolución de delegado

pseudo código:

@interface SomeClass <CLLocationManagerDelegate> 
... 
@end 

@implementation SomeClass 

... 

- (void)someMethod 
{ 
    CLLocationManager* locManager = [[CLLocationManager alloc] init]; 
    locManager.delegate = self; 
    [locManager startUpdatingLocation]; 
} 

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation 
{ 
    // Do something with the location 
    // ... 

    [manager stopUpdatingLocation]; 
    manager.delegate = nil; 
    [manager release]; 
} 

@end 

Me pregunto si este patrón de uso se considera que es siempre bien, si se considera que nunca está bien, o si depende de la clase de?

Hay un caso obvio en el que liberar el objeto de delegación salía mal y eso es si tiene que hacer algo después de haber notificado al delegado. Si el delegado libera el objeto, su memoria puede sobrescribirse y la aplicación falla. (Eso parece ser lo que ocurre en mi aplicación con CLLocationManager en una circunstancia particular, ambos solo en el simulador. Estoy tratando de averiguar si es un error del simulador o si lo que estoy haciendo es fundamentalmente defectuoso)

He estado buscando y no puedo encontrar una respuesta definitiva a esto. ¿Alguien tiene una fuente autorizada que pueda responder esta pregunta?

+0

Si ve un bloqueo - la otra cosa que podría * intentar * sería establecerlo en * liberación automática * en lugar de soltarlo explícitamente. Sin embargo, al no saber de manera concluyente, esto podría estar ofuscando el problema, en lugar de solucionarlo de verdad ... – Brad

+0

Interesante, no lo había considerado. Sin embargo, me pregunto qué grupo de autorrelease tomará esto en cuenta. – Hollance

+1

No estoy seguro, pero de alguna manera hacer esto se siente mal. –

Respuesta

5

Esta es una muy buena pregunta, esperé unas horas con la esperanza de que alguien diera una respuesta suficiente, pero como nadie respondió, lo intentaré. Primero comentaré sobre tu enfoque, luego intentaré sugerir cómo voy a solucionar esto.

Definitivamente es una muy mala idea lanzar - así desasignar un objeto de su delegado. Solo piense en cómo los objetos (como un CLLocationManager) llaman a sus delegados, simplemente los llaman en medio de algún método. Cuando la llamada al delegado finaliza, la ejecución del código vuelve a un método de un objeto que ya ha sido desasignado. BAM!

Olvidemos por un momento el hecho de que esta es una mala idea. Veo dos opciones cómo arreglar tan fácilmente. En primer lugar, autorelease en lugar de release le da a un objeto un poco más de tiempo de spam - al menos sobreviviría al regresar del delegado. Eso debería ser suficiente para la mayoría de los casos, al menos si el autor de API hizo bien su trabajo y encapsuló la lógica detrás de la clase API principal (en el caso de CLLocationManager podría estar esperando que el GPS se apague ...). La segunda opción sería retrasar la liberación (me viene a la mente el número performSelector:withObject:afterDelay:), pero eso es más una solución para las API mal implementadas.

Entonces, si liberarlo no es una buena idea, entonces ¿qué es?

Bueno, ¿qué ganas al liberar un CLLocationManager? Liberar esos pocos bytes de memoria no evitará que su aplicación finalice cuando el sistema se haya quedado sin memoria. De todos modos, ¿es realmente solo una vez que necesita la ubicación del usuario actual?

Sugiero que encapsule las tareas relacionadas con CLLocationManager en una clase separada, probablemente incluso un singleton, esa clase se convertiría en su delegado, y se encargaría de comunicarse con CLLocationManager e informar a su aplicación los resultados (probablemente enviando NSNotification) CLLocationManager se liberaría de esa clase dealloc, y nunca como resultado de una devolución de llamada delegada. stopUpdatingLocation debería ser suficiente, liberando pocos bytes de memoria, bueno, puede hacerlo cuando su aplicación esté ingresando el fondo, pero mientras se ejecute su aplicación, liberar esos pocos bytes no mejora significativamente el consumo de memoria.

** ** Adición

Es natural y correcto, para un delegado de tener la propiedad de un objeto para el cual actúa como delegado. Pero el delegado no debería liberar el objeto como resultado de una devolución de llamada. Sin embargo, hay una excepción a esto, y es la devolución de llamada que le informa que el procesamiento ha terminado. Como un ejemplo para esto es NSURLConnection 's connectionDidFinishLoading: que indica en la documentación "El delegado no recibirá más mensajes". Puede tener una clase que descarga un montón de archivos, cada uno con un NSURLConnection diferente (teniendo su clase como delegado), asignando Y soltándolos a medida que avanza la descarga de archivos.

CLLocationManager El comportamiento es diferente. Debería tener solo una instancia de CLLocationManager en su programa. Esa instancia está gestionada por algún código, probablemente un singleton, uno que podría lanzarse cuando la aplicación entra en segundo plano y reiniciarse cuando se despierte. La vida útil de CLLocationManager sería la misma que la de su clase administradora, que también actúa como delegada.

+0

Todos los puntos buenos. Sé cómo solucionar el problema, solo me pregunto qué es lo correcto. Una de las razones por las que hago esta pregunta es que ciertas fuentes semi autoritativas (como el Libro de cocina de Erica Sadun) liberan el objeto delegado de las devoluciones de llamadas de los delegados en ciertos ejemplos. Entonces, ¿está bien en ciertos casos (y cómo se sabe en qué casos es esto) o estos ejemplos son simplemente incorrectos? – Hollance

+0

La publicación siempre es mala idea. Autoreleasing, depende, podría funcionar, pero no hay garantía. Las API deben escribirse de forma tal que una clase Manager actúe como una interfaz entre la biblioteca y el código del usuario, para que el usuario pueda liberarla y la biblioteca pueda administrarse por sí misma hasta la limpieza completa (conexiones de red, hardware, corrección de GPS, lo que sea) . Ponga todas estas dudas a un lado y mantenga el objeto, especialmente si admite la suspensión como 'stopUpdatingLocation' en el caso de' CLLocationManager'. – Michal

+0

porque el comentario tiene una duración limitada, agregué más en la parte inferior de la respuesta. – Michal

5

No. Ese patrón siempre se considera incorrecto. Se rompe el Cocoa Memory Management Rules. El objeto administrador se pasó como un parámetro. No lo obtuvo por nuevo, alloc o copy, ni lo retuvo. Por lo tanto, no debe liberarlo.

+0

Pero I * did * asignó la instancia, y ese es el puntero que obtengo en el método delegado. Podría haber hecho de 'locManager' una variable de instancia y haberlo liberado en lugar de la variable' manager' local, pero esa sigue siendo la misma instancia que estoy lanzando. Por lo tanto, no cambia la naturaleza de mi pregunta. – Hollance

+2

@Hollance: no en ese ámbito no lo hizo. Las reglas son claras. Usted * no debe * liberar el objeto. Se le pasó como un parámetro, por lo tanto, la persona que llama tiene derecho a esperar que ** no ** haga algo para que desaparezca. – JeremyP

+0

Quien haya votado, explique por qué. Mi respuesta es objetivamente correcta. – JeremyP

3

Al igual que Michal, dijo que no hay absolutamente ninguna buena razón para liberar el objeto administrador para el ahorro de memeory. Además, al igual que dijo Jeemy, sería completamente incorrecto en cuanto a patrones y en cuanto al diseño liberar un objeto dentro de otra función que solo recibe ese objeto. Va en contra de todas las reglas.

Sin embargo, lo que sería correcto es simplemente detener las actualizaciones y establecer los administradores delegar en cero como ya lo está haciendo. Entonces, lo único que necesita eliminar es la línea [release manager].

Supongo que está creando un administrador en un ámbito local y, por lo tanto, está intentando averiguar cómo asegurarse de que se libere el administrador. Lo correcto aquí sería crear un administrador como una variable de instancia de su clase y luego simplemente liberarlo en la clase dealloc.

1

Otra razón por la cual su patrón es una mala idea, es que para algo como un CLLocationManager generalmente quiere decirle que deje de recibir actualizaciones si la pantalla se queda dormida; solo puede hacer eso si mantiene una referencia en algún lugar que puedes decir que empieces/pare. Y si mantiene una referencia, entonces también podría administrarla por completo.

+0

Es cierto, pero no del todo relevante para esta pregunta. Pude haber guardado 'locManager' como una variable de instancia.Pero aún me gustaría lanzar el CLLocationManager una vez que haya terminado con él, después de lo cual ya no necesito una referencia. – Hollance

Cuestiones relacionadas