Pregunta

Estoy tratando de hacer una cosa simple; lea una imagen de Internet, guárdela en el directorio de documentos de la aplicación en el iPhone y vuelva a leerla desde ese archivo para que pueda hacer otras cosas con ella más adelante. Escribir el archivo funciona bien, pero cuando intento leerlo nuevamente, obtengo un error EXC_BAD_ACCESS en GDB que no tengo idea de cómo resolverlo. Aquí es cómo se ve básicamente mi código:

-(UIImage *) downloadImageToFile {
NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths objectAtIndex:0];

[paths release]
NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

NSData * data = [[NSData alloc] initWithContentsOfURL:url];

[data writeToFile:path atomically:YES];

return [[UIImage alloc] initWithContentsOfFile:path];
}

El código falla en la declaración de retorno cuando intento inicializar la UIImage desde el archivo. ¿Alguna idea?

Editar : se olvidó de agregar una versión que fue el problema en el código originalmente.

¿Fue útil?

Solución

Su código muestra una gran falta de conocimiento de cómo funciona la administración de memoria en Objective-C. Además de los errores EXC_BAD_ACCESS que está recibiendo, la administración incorrecta de la memoria también provoca pérdidas de memoria que, en un dispositivo pequeño como el iPhone, pueden provocar bloqueos aleatorios.

Te recomiendo que le des esta lectura completa:

Introducción a la Guía de programación de la gestión de la memoria para Cocoa

Otros consejos

Nota: Esto se aplica específicamente a la administración de memoria sin ARC .

Dado que esto ha tenido tantas vistas y la respuesta verificada indica adecuadamente que "el código muestra una gran falta de conocimiento de cómo funciona la administración de memoria en Objective-C", " sin embargo, nadie ha señalado los errores específicos, me imagino que agregaría una respuesta que los afectó.

La regla de nivel de línea de base que debemos recordar sobre los métodos de llamada:

  • Si la llamada al método incluye las palabras asignar , nuevo , copiar o retener , tenemos la propiedad del objeto creado. & # 185; Si tenemos la propiedad de un objeto, es nuestra responsabilidad liberarlo.

  • Si la llamada al método no contiene esas palabras, no tenemos la propiedad del objeto creado. & # 185; Si no tenemos la propiedad de un objeto, liberarlo no es nuestra responsabilidad y, por lo tanto, nunca debemos hacerlo.

Veamos cada línea del código del OP:

-(UIImage *) downloadImageToFile {

Comenzamos un nuevo método. Al hacerlo, hemos comenzado un nuevo contexto en el que vive cada uno de los objetos creados. Tenga esto en cuenta por un momento. La siguiente línea:

    NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

Somos dueños de url : la palabra alloc allí nos dice que tenemos la propiedad del objeto y que necesitaremos liberarlo nosotros mismos. Si no lo hacemos, entonces el código perderá memoria.

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

No poseemos path : no usamos las cuatro palabras mágicas, por lo que no tenemos propiedad y nunca debemos liberarlas por nosotros mismos.

    NSString *documentsDirectory = [paths objectAtIndex:0];

No somos propietarios de documentsDirectory : sin palabras mágicas = sin propiedad.

    [paths release]

Al retroceder un par de líneas, vemos que no poseemos rutas , por lo que esta versión causará un bloqueo EXC_BAD_ACCESS cuando intentemos acceder a algo que ya no existe.

    NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

No somos propietarios de ruta : sin palabras mágicas = sin propiedad.

    NSData * data = [[NSData alloc] initWithContentsOfURL:url];

Somos dueños de data : la palabra alloc dice que tenemos propiedad del objeto y que necesitaremos liberarlo nosotros mismos. Si no lo hacemos, entonces el código perderá memoria.

Las siguientes dos líneas no crean ni liberan nada. Luego viene la última línea:

}

El método ha terminado, por lo que el contexto para las variables ha finalizado. Mirando el código, podemos ver que poseíamos tanto url como data , pero no liberamos ninguno de ellos. Como resultado, nuestro código perderá memoria cada vez que se llame a este método.

El NSURL objeto url no es muy grande, por lo que es posible que nunca notemos la fuga, aunque aún debe limpiarse, no hay razón para filtrarlo

El NSData objeto data es una imagen png, y podría ser muy grande; estamos perdiendo todo el tamaño del objeto cada vez que se llama a este método. Imagine que se llamaba cada vez que se dibujaba una celda de la tabla: no tardaría mucho en bloquear la aplicación por completo.

Entonces, ¿qué debemos hacer para solucionar los problemas? Es bastante simple, simplemente necesitamos liberar los objetos tan pronto como ya no los necesitemos, generalmente después de la última vez que se usan:

-(UIImage *) downloadImageToFile {

    // We own this object due to the alloc
    NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

    // We don't own this object
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    // We don't own this object
    NSString *documentsDirectory = [paths objectAtIndex:0];

    //[paths release] -- commented out, we don't own paths so can't release it

    // We don't own this object
    NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

    // We own this object due to the alloc
    NSData * data = [[NSData alloc] initWithContentsOfURL:url];

    [url release]; //We're done with the url object so we can release it

    [data writeToFile:path atomically:YES];

    [data release]; //We're done with the data object so we can release it

    return [[UIImage alloc] initWithContentsOfFile:path];

    //We've released everything we owned so it's safe to leave the context
}

Algunas personas prefieren liberar todo de una vez, justo antes de que el contexto se cierre al final del método. En ese caso, tanto [url release]; como [data release]; aparecerían justo antes de la llave de cierre } . Encuentro que si los libero tan pronto como pueda, el código es más claro, dejándolo claro cuando lo repase exactamente donde termino con los objetos.

Para resumir: poseemos objetos creados con alloc , new , copy o keep en el método Las convocatorias deben liberarlas antes de que finalice el contexto. No poseemos nada más y nunca debemos liberarlos.


& # 185; En realidad, no hay nada mágico en las cuatro palabras, solo son un recordatorio que las personas de Apple crearon los métodos en cuestión. Si creamos nuestros propios métodos de inicialización o copia para nuestra propia clase, entonces la inclusión de las palabras asignar, nuevo, copiar o retener en sus métodos apropiados es nuestra responsabilidad, y si no los usamos en nuestros nombres, entonces Tendremos que recordar por nosotros mismos si la propiedad ha pasado.

Una cosa que me ayuda mucho es tener un punto de interrupción en objc_exception_throw. Cada vez que estoy a punto de recibir una excepción, llego a este punto de interrupción y puedo hacer una copia de seguridad de la cadena de apilamiento. Solo dejo este punto de interrupción habilitado todo el tiempo en mis proyectos de iPhone.

Para hacer esto, en xcode ve a cerca de la parte inferior del panel izquierdo " Grupos & amp; Archivos " y encuentre " Puntos de Interrupción " ;. Ábralo y haga clic en Puntos de interrupción del proyecto y en el panel de detalles (arriba), verá un campo azul con la etiqueta " Haga doble clic para obtener el símbolo " Haz doble clic en él e ingresa " objc_exception_throw " ;.

La próxima vez que lance una excepción, se detendrá y en el depurador, puede volver a subir la cadena de apilamiento a su código que causó la excepción.

Definitivamente, dale una revisión rápida a las reglas de administración de memoria. Nada salta que podría causar el error que está recibiendo, pero está filtrando todos esos objetos que está asignando. Si no entiende el patrón de retención / liberación, es probable que haya otro punto en su código donde no retenga un objeto correctamente, y eso es lo que está causando el error EXC_BAD_ACCESS.

También tenga en cuenta que NSString tiene métodos para tratar con las rutas del sistema de archivos, nunca debería tener que preocuparse por el separador.

Sin embargo, en general, si obtienes EXC_BAD_ACCESS en tu código y no puedes averiguar por qué, intenta usar NSZombie (no, no estoy bromeando).

En Xcode, expanda la sección Ejecutables a la izquierda. Haga doble clic en la lista que tiene el mismo nombre que su proyecto (debe ser el único). En la ventana que aparece, vaya a Argumentos y, en la parte inferior, haga clic en el botón más. El nombre debe ser NSZombieEnabled y el valor debe establecerse en YES

De esta manera, cuando intentes acceder a un objeto liberado, tendrás un mejor desempeño de lo que estás haciendo. Solo establece el valor en NO una vez que hayas descubierto el error.

Espero que esto ayude a alguien!

Estos errores se producen cuando no administra correctamente la memoria (es decir, un objeto se libera prematuramente o es similar)

Intenta hacer algo como lo siguiente ...

UIImage *myImage = [[UIImage alloc] initWithContentsOfFile:path];
return [myImage autorelease];

Pasé mucho tiempo experimentando al mismo tiempo que me familiarizaba con los conceptos de release / autorelease. A veces, la palabra clave de retención también debe reproducirse (aunque probablemente no en este caso)

Otra opción podría ser simplemente que la ruta no existe o no se puede leer desde ella.

¿Quizás, initWithContentsOfFile no toma un argumento de ruta? Busque en los diferentes métodos de inicio de UIImage, creo que hay uno diferente para aceptar una ruta.

También podría haber algo más elegante que tengas que hacer para hacer un camino. Recuerdo haber hecho algo con " paquetes " ;? Lamento ser tan vago, es todo lo que recuerdo de repente.

quite la barra del camino y asegúrese de que esté en el proyecto. no importa si está en ese directorio, pero debe agregarse al proyecto para que usted pueda acceder a él.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top