Domanda

Sto cercando di fare una cosa semplice; leggi un'immagine da Internet, salvala nella directory dei documenti dell'app sull'iPhone e rileggila da quel file in modo da poter fare altre cose con esso in seguito. Scrivere il file funziona bene ma quando provo a rileggerlo ottengo un errore EXC_BAD_ACCESS in GDB che non ho idea di come risolvere. Ecco come appare sostanzialmente il mio codice:

-(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];
}

Il codice non riesce nell'istruzione return quando provo a inizializzare UIImage dal file. Qualche idea?

Modifica : trascurato di aggiungere una versione che era il problema originariamente nel codice.

È stato utile?

Soluzione

Il tuo codice mostra una grave mancanza di conoscenza del funzionamento della gestione della memoria in Objective-C. Oltre agli errori EXC_BAD_ACCESS che ricevi, una gestione impropria della memoria provoca anche perdite di memoria che, su un piccolo dispositivo come l'iPhone, possono causare arresti anomali casuali.

Ti consiglio di leggere questo thorogh:

Introduzione alla Guida alla programmazione della gestione della memoria per Cocoa

Altri suggerimenti

Nota: questo si applica specificamente alla gestione della memoria non-ARC .

Dato che questo ha avuto così tante visualizzazioni e la risposta selezionata afferma in modo appropriato che "il codice mostra una grave mancanza di conoscenza di come funziona la gestione della memoria in Objective-C," eppure nessuno ha segnalato gli errori specifici, immagino che aggiungerei una risposta che li abbia toccati.

La regola a livello di base che dobbiamo ricordare sui metodi di chiamata:

  • Se la chiamata del metodo include le parole alloc , nuovo , copia o conserva , abbiamo la proprietà dell'oggetto creato. & # 185; Se possediamo un oggetto, è nostra responsabilità liberarlo.

  • Se la chiamata del metodo non contiene queste parole, non abbiamo la proprietà dell'oggetto creato. & # 185; Se non è proprietario di un oggetto, rilasciarlo non è non nostra responsabilità e pertanto non dovremmo mai farlo.

Diamo un'occhiata a ciascuna riga del codice OP:

-(UIImage *) downloadImageToFile {

Abbiamo iniziato un nuovo metodo. In tal modo abbiamo iniziato un nuovo contesto in cui vive ciascuno degli oggetti creati. Tienilo a mente per un po '. La riga successiva:

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

Possediamo url : la parola alloc ci dice che abbiamo la proprietà dell'oggetto e che dovremo rilasciarlo da soli. In caso contrario, il codice perderà memoria.

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

Non possediamo percorsi : nessun uso delle quattro parole magiche, quindi non abbiamo proprietà e non dobbiamo mai rilasciarlo da soli.

    NSString *documentsDirectory = [paths objectAtIndex:0];

Non possediamo documentiDirectory : nessuna parola magica = nessuna proprietà.

    [paths release]

Tornando indietro di un paio di righe vediamo che non possediamo percorsi , quindi questa versione provocherà un arresto anomalo di EXC_BAD_ACCESS mentre proviamo ad accedere a qualcosa che non esiste più.

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

Non possediamo percorso : nessuna parola magica = nessuna proprietà.

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

Possediamo dati : la parola alloc indica che abbiamo la proprietà dell'oggetto e che dovremo rilasciarlo da soli. In caso contrario, il codice perderà memoria.

Le seguenti due righe non creano o rilasciano nulla. Quindi arriva l'ultima riga:

}

Il metodo è terminato, quindi il contesto per le variabili è terminato. Guardando il codice possiamo vedere che possedevamo sia url che data , ma non li abbiamo rilasciati. Di conseguenza il nostro codice perderà memoria ogni volta che viene chiamato questo metodo.

L'oggetto NSURL url non è molto grande, quindi è possibile che non possiamo mai notare la perdita, anche se dovrebbe comunque essere pulita, non c'è motivo di perdilo.

L'oggetto NSData data è un'immagine png e potrebbe essere molto grande; stiamo perdendo l'intera dimensione dell'oggetto ogni volta che viene chiamato questo metodo. Immagina che questo venga chiamato ogni volta che viene disegnata una cella di tabella: non ci vorrebbe molto a mandare in crash l'intera app.

Quindi cosa dobbiamo fare per risolvere i problemi? È piuttosto semplice, dobbiamo semplicemente rilasciare gli oggetti non appena non ne abbiamo più bisogno, di solito subito dopo l'ultima volta che vengono utilizzati:

-(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
}

Alcune persone preferiscono rilasciare tutto in una volta, proprio prima che il contesto si chiuda alla fine del metodo. In tal caso sia [url release]; che [data release]; appariranno subito prima della parentesi graffa } . Trovo che se li rilascio appena posso il codice è più chiaro, chiarendolo quando lo ripasso più tardi esattamente dove ho finito con gli oggetti.

Per riassumere: possediamo oggetti creati con alloc , new , copia o conserva nel metodo le chiamate devono quindi rilasciarle prima della fine del contesto. Non possediamo nient'altro e non dobbiamo mai rilasciarli.


& # 185; Non c'è nulla di veramente magico nelle quattro parole, sono solo un promemoria usato coerentemente dalla gente di Apple che ha creato i metodi in questione. Se creiamo i nostri metodi di inizializzazione o copia per una nostra classe, l'inclusione delle parole alloc, new, copy, o keep nei suoi metodi appropriati è la nostra responsabilità e se non li usiamo nei nostri nomi, allora noi dovremo ricordare da soli se la proprietà è passata.

Una cosa che mi aiuta molto è avere un breakpoint su objc_exception_throw. Ogni volta che sto per ricevere un'eccezione, raggiungo questo breakpoint e riesco a eseguire il debug della catena dello stack. Lascio questo punto di interruzione sempre abilitato nei miei progetti iPhone.

Per fare ciò, in xcode vai vicino alla parte inferiore del riquadro sinistro " Gruppi & amp; File " e trova "punti di interruzione". Aprilo e fai clic su Punti di interruzione progetto e nel riquadro dei dettagli (in alto), vedrai un campo blu etichettato " Fai doppio clic per il simbolo. & Quot; Fai doppio clic su di esso e inserisci " objc_exception_throw " ;.

La prossima volta che lanci un'eccezione, ti fermerai e nel debugger, puoi risalire la catena dello stack al tuo codice che ha causato l'eccezione.

Assegna sicuramente una rapida revisione alle regole di gestione della memoria. Non salta fuori nulla che causerebbe l'errore che stai ricevendo, ma stai perdendo tutti quegli oggetti che stai allocando. Se non capisci il modello di conservazione / rilascio, è probabile che ci sia un altro punto nel tuo codice in cui non stai trattenendo correttamente un oggetto, e questo sta causando l'errore EXC_BAD_ACCESS.

Nota anche che NSString ha metodi per gestire i percorsi del filesystem, non dovresti mai preoccuparti del separatore.

In generale, tuttavia, se ricevi EXC_BAD_ACCESS nel tuo codice e non riesci per la vita a capire perché, prova a usare NSZombie (no, non sto scherzando).

In Xcode, espandi la sezione degli eseguibili a sinistra. Fai doppio clic sull'elenco con lo stesso nome del tuo progetto (dovrebbe essere l'unico). Nella finestra che si apre, vai su Argomenti e, nella parte inferiore, fai clic sul pulsante più. Il nome deve essere NSZombieEnabled e il valore deve essere impostato su YES

In questo modo, quando provi ad accedere a un oggetto rilasciato, avrai una visione migliore di ciò che stai facendo. Basta impostare il valore su NO una volta individuato il bug.

Spero che questo aiuti qualcuno!

Questi errori si verificano quando si gestisce male la memoria (ad es. un oggetto viene rilasciato prematuramente o simili)

Prova a fare qualcosa del tipo ..

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

Ho passato molto tempo a sperimentare mentre mi occupavo dei concetti di rilascio / rilascio automatico. A volte è necessario riprodurre anche la parola chiave keep (anche se probabilmente non in questo caso)

Un'altra opzione potrebbe essere semplicemente che il percorso non esiste o non può essere letto da?

Forse initWithContentsOfFile non accetta un argomento sul percorso? Sfoglia i diversi metodi init per UIImage, penso che ce ne sia uno diverso per accettare un percorso.

Potrebbe esserci anche qualcosa di più fantasioso che devi fare per creare un percorso? Ricordo di aver fatto qualcosa con " bundles " ;? Mi dispiace essere così vago, è tutto ciò che ricordo di persona.

togli la barra dal percorso e assicurati che sia nel progetto. non importa se si trova in quella directory, ma deve essere aggiunto al progetto per accedervi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top