2009-02-13 7 views
7

Conozco los principios básicos sobre administración de memoria (retención de conteo, autorelease piscinas, etc.) en Cocoa, pero una vez que pasa más allá de retención/liberación simple, se vuelve un poco más confuso No pude encontrar respuestas decentes para ellos, ya que la mayoría de los tutoriales cubren escenarios simples. Me gustaría preguntar acerca de las mejores prácticas sobre cómo escribir el código y evitar fugas.Administración de memoria Objective-C, analizador xml y otros ejemplos no triviales


primera pregunta - iteraciones y asignaciones temporales:

for (id object in objectArray) {  
    Model *currentItem = object; 
    /* do something with currentItem */ 
    [currentItem release]; 
} 

Si quito la liberación en la última línea del código funciona bien, pero no hay fugas. ¿Cuál es la regla aquí? El objeto ya está allí en objectArray. Lo estoy asignando directamente, para obtener el tipo. ¿Debería hacer esto de otra manera? ¿Esta asignación aumenta retenciónCuenta de currentItem? (¿Es algo así como [[alloc] initWithObject]?) ¿Cómo saber si esta asignación (objeto) se libera automáticamente o no?


pregunta segundo - instantánea conserva:

Model *model = [unarchiver decodeObjectForKey:@"ARCHIVED_MODEL_OBJECT"]; 
// it has to be here, because (I was told) unarchiver will return autorelease object  
[model retain]; 
label.text = model.data; 

cómo alguien sabía que este método particular trabaja tan extraño, que tengo que llamar inmediatamente a retener el valor devuelto, o voy a topar en nulo en el próximo ¿asignación? No pude encontrar nada como esto en la documentación. De acuerdo con las reglas de retención/liberación, esperaría que decodeObjectForKey devuelva el objeto autorelasado, pero tomará un tiempo hasta que el control regrese a la aplicación y el grupo reclame el objeto modelo que se lanzará. ¿Hay alguna regla para eso? ¿Cómo debo buscar esos?


3ª Pregunta - autoreleasing y que pasan variables:

- (IBAction) loadXMLButtonClicked:(id) sender { 
    objectArray = [self loadData]; // 1 - objectArray is instance var 
    NSArray *objectArray = [self loadData]; // 2 - objectArray is local var 

    // loadXMLButtonClicked is called on button click, here the method finishes 
    // and control goes back to application, autorelease pool is cleaned? 

    // case 1 - objectArray stays retained in instance variable? (because setter was used) 
    // case 2 - objectArray is soon to be released, there were no retains? 
    // (ignore the fact that it's local var, just hypothetically) 

} 

- (NSArray *) loadData { 
    NSArray *objectArray = [[NSArray alloc] init]; 
    // populate array here 
    return [objectArray autorelease]; 
} 

cuarta pregunta - (tengan paciencia conmigo, último) analizador XML mejores prácticas: (no quiero usar otras soluciones, usando el analizador estándar es practicar el flujo y la administración de la memoria objetivo-c)

Básicamente este código aquí funciona, funciona bien y no tiene fugas, pero realmente no sé si esto es el enfoque adecuado. Tengo un objeto separado que actúa como analizador, analiza un XML para recopilar una matriz de objetos del tipo Modelo. Ahora, después de realizar el análisis, externamente me gustaría obtener esa matriz, aunque no sé cómo (está copiando la matriz y lanzando una buena idea para el analizador completo). Por favor revise el código y vea los comentarios.

He depurado este código muchas veces, usando gdb para imprimir retenerCounts, zombies, etc. y aunque puedo conseguirlo sin fugas, no sé el 100% por qué y me gustaría escuchar un buen razonamiento, ¿cómo debe hacerse esto con la explicación? Eso sería muy apreciado.

Controller.m

- (NSArray *) loadData { 
(...) 
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; 
ModelXMLParser *parserDelegate = [[ModelXMLParser alloc] init]; 
[parser setDelegate:parserDelegate]; 
[parser parse]; 

objectArray = [[parserDelegate objectArray] copy]; 
// is this ok? *i* don't need the parser object so I think I should get rid of it 
// and copy the data. How this copy works, is it shallow (only new reference to array) 
// or deep copy (objects allocated again as well)? 
// how to do deep copy of NSArray? 

[parserDelegate release]; 
[parser release]; 
} 

ModelXMLParser.m (simplificado)

@implementation ModelXMLParser 

@synthesize objectArray; // array of objects 
@synthesize currentObject; // temporary object 
@synthesize currentChars; // temporary chars 
@synthesize parseChars; // parse chars only when there's need, leave those /t/n etc 

- parser didStartElement (...) { 
    if ([elementName isEqualToString:@"objects"]) { 
     objectArray = [[NSMutableArray alloc] init]; 
    } 
    else if ([elementName isEqualToString:@"object"]) { 
     currentObject = [[Model alloc] init]; 
    } 
    else if ([elementName isEqualToString:@"name"]) { 
     // do I have to init currentObject.name (NSString) here? I guess not 
     [self setParseChars:YES]; // just set the flag to make parse control easier 
    } 
    else if ([elementName isEqualToString:@"number"]) { 
     // int isn't object anyway, no init 
     [self setParseChars:YES]; // just set the flag to make parse control easier 
    } 
} 

- parser foundCharacters (...) { 
    if (parseChars) { 
     currentChars = [[NSString alloc] initWithString:string]; 
     // why is currentChars retainCount = 2 here? 
     // is it like currentChars = [NSString new] and then currentChars = string? (so retain once more) 
     // is it good way to control parser? (please ignore the NSMutableString and appending example, try this one) 
     // should I just do currentChars = string here? 

     [currentChars autorelease]; // this is currently my solution, because there's no leak, but I feel it's incorrect 
    } 
} 

- parser didEndElement (...) { 
    if ([elementName isEqualToString:@"object"]) { 
     [objectArray addObject:[currentObject copy]]; // should I copy here or just addObject, it retains anyway? 
     [currentObject release]; // I've initialized currentObject before, now I don't need it, so I guess retainCount goes to 0 here? 
    } 
    else if ([elementName isEqualToString:@"name"]) { 
     currentObject.name = currentChars; // is this correct, or shoud I do [currentChars copy] as well? 
     [self setParseChars:NO]; 
     [currentChars release]; // as before, initialized, now releasing, but is this really correct? 
    } 
    else if ([elementName isEqualToString:@"number"]) { 
     currentObject.number = [currentChars intValue]; // is this correct, or shoud I do [currentChars copy] as well? 
     [self setParseChars:NO]; 
     [currentChars release]; // as before, initialized, now releasing, but is this really correct? 
    } 
} 

- (void) dealloc { 
    // I shouldn't release currentChars or currentObject, those (I suppose) should be freed after parsing done, 
    // as a result of earlier releases? 

    [objectArray release]; 
    [super dealloc]; 
} 

modelo.m

@implementation Model 

@synthesize name; // this is NSString 
@synthesize number; // this is int 

- (id) copyWithZone:(NSZone *) zone { 
    Model *copy = [[[self class] allocWithZone:zone] init]; 
    copy.name = [self.name copy]; 
    copy.number = self.number; 
    return copy; 
} 

- (void) dealloc { 
    [name release]; 
    // I don't have to release int, right? it's not an object 
    [super dealloc]; 
} 

estoy especialmente confundida con la pregunta 4. Lo siento por tal vez demasiado largo pregunta, pero esto en realidad se trata de un tema y más profunda comprensión de la misma.

Respuesta

7

primera pregunta - iteraciones y funciones temporales

NO debe ser la liberación de los objetos aquí, ya que no es un propietario. Solo debe liberar un objeto si es dueño de él. Vea el Memory Management Guide for Cocoa. Usted es el único propietario de un objeto si llama a un método cuyo nombre comienza con init, new, o contiene copy en su nombre.

Dado que for loop no utiliza métodos con ninguno de esos nombres, usted no es un propietario, por lo que NO DEBE liberar los objetos. Esto dará como resultado la liberación de objetos antes de que terminen, lo que casi con certeza provocará daños en la memoria y un bloqueo.

pregunta segundo - instantánea conserva:

No es necesario que al instante llamada a retener, sólo tiene que llamar antes de la piscina autorelease próximos vacía. Probablemente, esto será poco después de que su método regrese al ciclo del evento principal. Como no sabe exactamente cuándo sucederá esto, debe asegurarse de que si desea poder acceder al objeto después de que la función (loadXMLButtonClicked: en este caso) regrese, debe hacerlo al retain antes de regresar.

Desde decodeObjectForKey no comienza con init o new o contienen copy en su nombre, no se están convirtiendo en un propietario. Llamar al retain te convierte en propietario.

3ª pregunta - autoreleasing y pasar variables:

En primer lugar, es una mala práctica a la sombra de un miembro de la clase con una variable local con el mismo nombre. En segundo lugar, a menos que loadData se esté utilizando como una función de utilidad multipropósito (que supongo que no lo es porque no toma ningún parámetro), simplemente debe asignar el resultado directamente a la variable miembro objectArray. No tiene sentido y es propenso a errores devolver el resultado y luego asignar la función de llamada a la variable miembro.

En tercer lugar, no está utilizando el conjunto de propiedades objectArray; solo lo está asignando directamente a la variable miembro. Si desea utilizar el colocador, debe indicar explícitamente self.objectArray = ... (con el self. frente a él). Por lo tanto, objectArray nunca se conserva, por lo que se desasignará la próxima vez que se libere el grupo de liberación automática, que no es lo que desea. Debe conservarlo en algún punto, o viceversa, simplemente no lo libere al final de loadData, y asígnele la variable de miembro de clase objectArray.

Si la propiedad se declara con el atributo retain, a continuación, utilizando la incubadora llamará automáticamente a retain cuando se asigna con él (y también lo hará release el valor anterior). Si, en cambio, la propiedad se declara con el atributo copy, el valor se copiará cada vez que se le asigne y se convertirá en propietario del nuevo objeto.

cuarta pregunta - analizador XML mejores prácticas:

Usted está haciendo una copia superficial del objeto de matriz. Si desea hacer una copia profunda, puede usar el mensaje initWithArray:copyItems:.

¿Tengo que iniciar currentObject.name (NSString) aquí? ¿Supongo que no?

No entiendo esta pregunta, no hay ningún currentObject.name mencionado en ninguna parte cerca de ese código.

¿por qué es currentChars retainCount = 2 aquí?

Probablemente debido a que durante su proceso de inicialización interna, que era retain ed un tiempo extra en alguna parte, pero también era casi seguro que autoreleased un tiempo extra. Si sigue todas las reglas de la Guía de administración de memoria para Cocoa, no tendrá ningún problema. Nunca debe confiar en retener conteos, ya que no sabe cuántas veces se ha liberado automáticamente un objeto. Son una ayuda de depuración, no algo que debería usarse para el flujo de control del programa.

esta es actualmente mi solución, porque no hay fugas, pero creo que es incorrecta?

Si no necesita usar currentChars la próxima vez que regrese al bucle de evento, está bien.Si necesita usarlo, no debe soltarlo o liberarlo aquí, y luego libérelo cuando esté seguro de haberlo terminado.

debo copiar aquí o simplemente addObject, se conserva de todos modos?

Sólo addObject: cuando se agregan elementos en un NSArray, NSSet o NSDictionary, son automáticamente retain cado por la estructura de datos. Cuando los quites, son release d.

La mayoría del resto de las preguntas se puede responder simplemente siguiendo las reglas o con respuestas idénticas a algunas de las preguntas anteriores que ya he respondido.

1

@ primera pregunta

Sólo se tiene una referencia al objeto en el objectArray. Todavía está en ObjectArray que también tiene el objeto retenido y liberarlo no es bueno porque no hiciste nada que lo retuviera.

ve aquí por alguna rules

+0

Hace sentido para mí. Pero si no hago esto, tengo pérdida de memoria. La matriz de objetos es un objeto liberado automáticamente, así que no debería lanzarlo. ¿Por qué la fuga? – rattkin

+0

¿Cómo sabes que tienes una fuga? Epatel tiene razón, no debes liberar el objeto. ¿No tienes la oportunidad de hacer algo como [objectArray addObject: [[foo alloc] init]? Esa sería la verdadera causa de la fuga. – zoul

0

@ segunda pregunta

Parece que va a configurar una propiedad de texto UILabel, que en este caso utiliza copia. En la documentación que dice:

@property(nonatomic, copy) NSString *text; 

Esto significa que la etiqueta va a copiar y retener esa copia no cambiar o retener el objeto que se utiliza para asignar la propiedad.

+0

Sé que puedo hacer [liberación del modelo] después. La pregunta es por qué tengo que retener el modelo después de este método desarchivar? Si no hago esto, se liberará el modelo y fallará la asignación a la etiqueta. – rattkin

0

@ tercera pregunta

La línea "objectArray = [self loadData]; // 1 - objectArray is instance var" no es realmente un pionero, ya que tiene acceso a la variable de instancia directamente. Para utilizar un regulador uno necesita acceder a él sin embargo auto

self.objectArray = [self loadData]; 

... y si su propiedad se declara como (nonatomic, copy) la vieja objectArray se dará a conocer un nuevo y se creará una copia, por tanto, ser retenidos .

0

Gracias a todos por las respuestas. Esas respuestas me ayudaron, aunque solo en algunos lugares, el resto lo tuve que resolver yo mismo. Estaba mucho más centrado en la idea, no en la implementación línea por línea, pero era difícil describir aquí sin pegar grandes cantidades de código. Es solo que delegar para el análisis de objetos xml es un ejemplo muy específico, porque no devuelve valor per se, el valor debe tomarse y asignarse externamente.

Estoy marcando la respuesta de Adam como la mejor, como la más detallada, aunque no responda todos mis problemas.

Para otros - http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html es una lectura excelente. También leer mis propias respuestas a las preguntas:

1 - Por supuesto que no suelte ese objeto. La fuga de memoria no fue causada por esto.

2 - Este método no tiene nada de especial, simplemente devuelve un objeto simple liberado automáticamente, como otros intentaron explicarme aquí. La raíz de mis problemas iniciales fue que anteriormente no tenía ese retener, pero lo llamé [versión del modelo] poco después, lo que provocó que la liberación del envío del grupo de liberación automática fuera un objeto inexistente. No debería hacer [versión del modelo] porque no soy el propietario de ese objeto. De hecho, este retener aquí ni siquiera es necesario, porque solo necesito el objeto para obtener valor de él, y luego puedo modificarlo, por lo que se puede pasar de forma segura al grupo de liberación automática, sin retener.

3 - Pretendía que este método (loadData) fuera independiente, por lo tanto, no establecía ninguna variable de instancia, sino que devolvía la matriz para otras. Fue un ejemplo paralelo, no es que tenga dos variables con el mismo nombre en el método.

Si declaro un objeto dentro de este método (situación n. ° 2), sucede que se libera automáticamente al final de este método, porque una vez que finaliza, el control vuelve a la aplicación y se inicia el grupo de versiones. estaba bien conmigo en este ejemplo, porque no necesito el arreglo más adelante. En el mundo real, probablemente debería tener la variable de instancia (situación # 1) y luego ir con self.objectArray = [self loadData], porque esto lanzará setter y el objeto autoreleasado se mantendrá aquí.

4 - he aquí algunas cosas confusas. Básicamente estaba tratando de codificar en objecive-c con la gestión de memoria manual, pero aún tengo una actitud de "recolector de basura". Es muy importante recordar que si hace [[object alloc] init] y luego en un momento posterior [release del objeto] - ¡no tiene que ser automáticamente que el objeto será destruido! ¡La liberación no anula! Esta es, por supuesto, una regla fundamental (retener/liberar), pero incluso sabiéndolo, es fácil de olvidar. Haga un seguimiento de lo que está haciendo con su objeto entre esas dos líneas: el objeto puede vivir mucho después, porque "otra persona" se convertirá en su propietario.El método de liberación al final de esta clase LiveCycle no significa - objeto ahora se destruye, sino que significa: "ya no me importa, mis manos están limpias"

Así, línea por línea:

objectArray = [[parserDelegate objectArray] copy]; 

Esto está perfectamente bien, no copio en profundidad. Estoy copiando una matriz, lo que significa que asigna nueva memoria para el objeto de la matriz, pero no para los contenidos. PERO, la copia enviada a objectArray también envía retener a cada objeto. En mi ejemplo, estoy lanzando mi parserDelegate, que también lanza su propio objectArray, disminuyendo retainCount para cada objeto. Si no hago la copia aquí, los objetos llegarán a retainCount = 0 y se destruirán. De esta manera tengo una nueva matriz, con punteros a objetos antiguos, pero esencialmente se convierten en mis objetos, porque la matriz anterior se destruye, y debido a mi retención, me estoy convirtiendo en el propietario. Lo siento si esto es hablar demasiado, pero realmente tenía que concentrarme para entender esto.

else if ([elementName isEqualToString:@"name"]) { 
    // do i have to init currentObject.name (NSString) here? i guess not? 
    [self setParseChars:YES]; // just set the flag to make parse control easier 
} 

La pregunta aquí era, si debería inicializar la propiedad currentObject.name NSString, porque va a ser llenado poco después foundCharacters entra en acción. Ahora bien, esto es interesante. Cuando inicializa un objeto completo, sus propiedades NSString son nulas. Ahora, más tarde, lo hago

currentObject.name = currentChars; 

Lo que inicia setter. Este setter se define como (no atómico, retener), lo que significa que se conserva el nuevo valor, se libera el valor anterior y se asigna el puntero. Bastante gracioso, no importa si el valor anterior se inicializa o si es nulo: si se inicializa, se lanzará de todos modos, si es nulo, entonces aún puede aceptar el lanzamiento (aunque no estoy 100% seguro de ello) ?) - nada pasará. Pero en aras de la corrección, supongo que la línea inicial debe ser similar:

else if ([elementName isEqualToString:@"name"]) { 
    currentObject.name = [NSString new]; // just alloc/init routine 
    [self setParseChars:YES]; // just set the flag to make parse control easier 
} 

Ahora:

[currentChars autorelease]; 

no debería estar ahí. Este es un diseño confuso y fue una mala solución. Todo lo que debería estar allí, es solo cadena init.

[objectArray addObject:[currentObject copy]]; 

Copia no es necesaria aquí. addObject retendrá de todos modos. No tiene sentido crear otra asignación. Esta fue una de las causas de mis filtraciones.

Todo lo demás está bien. Debido a que estoy liberando currentChars justo después de establecer el valor de mi objeto, ahora lo conserva, y se convierte en el propietario, y lo estoy liberando "aquí" en el analizador, porque ya no lo necesito (será asignado en el siguiente ciclo).

Puede ser confuso, pero me imagino que habrá otros con problemas de asignación de memoria no estándar e incluso para personas con experiencia puede ser un desafío colocar las cosas en el lugar correcto. Tal vez mi historia te ayude entonces.

0

estático Analizador de

Además de: Memory Management Programming Guide for Cocoa, estático analizador es una herramienta indispensable.

Proyecto-> Proyecto Configuración-> Build-> Build Opciones-> Ejecutar analizador estático

asegúrese de que está marcada.

Te dirá todos los errores de asignación de memoria que estás haciendo.Por lo tanto, comprenderá mejor cómo crear objetos, errores de doble liberación automática, errores de liberación doble, referencia a objetos liberados, etc.

Leí los principios de administración de memoria muchas veces pero no los obtuve hasta que usé el Estático Analizador.

Ahora soy mejor en esto y lo hago bien la mayor parte del tiempo. Static Analyzer, sin embargo, sigue siendo esencial porque señala los errores y los errores.

Yoichi