Как устранить ошибки EXC_BAD_ACCESS, возникающие при разработке iphone?

Я пытаюсь сделать простую вещь; прочитайте изображение из Интернета, сохраните его в каталоге документов приложения на iphone и прочитайте его обратно из этого файла, чтобы я мог делать с ним другие вещи позже. Написание файла работает нормально, но когда я пытаюсь его прочитать, я получаю ошибку EXC_BAD_ACCESS в GDB, я не знаю, как ее решить. Вот как выглядит мой код:

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

Сбой кода в операторе return при попытке инициализировать UIImage из файла. Любые идеи?

Изменить: пренебречь добавлением релиза, который был проблемой в исходном коде.

Ответы

Ответ 1

Ваш код показывает серьезную нехватку знаний о том, как работает управление памятью в Objective-C. В дополнение к ошибкам EXC_BAD_ACCESS, которые вы получаете, неправильное управление памятью также вызывает утечку памяти, которая на маленьком устройстве, таком как iPhone, может привести к случайным сбоям.

Я рекомендую вам дать это торогом:

Введение в Руководство по программированию памяти для Cocoa

Ответ 2

Примечание. Это относится к управлению памятью не ARC.

Так как у этого было так много просмотров и проверенный ответ правильно заявляет, что "код показывает серьезную нехватку знаний о том, как работает управление памятью в Objective-C", но никто не указал конкретные ошибки, я полагаю, d добавьте ответ, который коснулся их.

Правило базового уровня, которое мы должны помнить о вызывающих методах:

  • Если вызов метода включает в себя слова alloc, новый, копировать или сохранить у нас есть собственность на созданный объект.¹ Если у нас есть собственность на объект, мы обязаны освободить его.

  • Если вызов метода не содержит этих слов, у нас нет права собственности на созданный объект.¹ Если у нас нет собственности на объект, освобождение его не является нашей ответственностью, и поэтому мы никогда не должны сделайте это.

Посмотрите на каждую строку код OP:

-(UIImage *) downloadImageToFile {

Мы начали новый метод. При этом мы начали новый контекст, в котором живет каждый из созданных объектов. Помните об этом немного. Следующая строка:

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

У нас есть url: слово alloc говорит нам, что мы имеем право собственности на объект и что нам нужно его самостоятельно выпустить. Если мы этого не сделаем, тогда код будет утечка памяти.

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

Мы не владеем paths: нет использования четырех волшебных слов, поэтому у нас нет собственности и мы никогда не должны ее выпускать.

    NSString *documentsDirectory = [paths objectAtIndex:0];

У нас нет documentsDirectory: нет волшебных слов = нет собственности.

    [paths release]

Возвращая пару строк, мы видим, что у нас нет собственных путей, поэтому этот выпуск приведет к сбою EXC_BAD_ACCESS, когда мы попытаемся получить доступ к тому, что больше не существует.

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

У нас нет path: нет волшебных слов = нет собственности.

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

У нас есть data: слово alloc говорит, что мы имеем право собственности на объект и что нам нужно будет его самостоятельно выпустить. Если мы этого не сделаем, тогда код будет утечка памяти.

Следующие две строки не создают и не выпускают ничего. Затем идет последняя строка:

}

Метод закончен, поэтому контекст для переменных закончился. Посмотрев на код, мы видим, что нам принадлежат как url, так и data, но не выпустили ни один из них. В результате наш код будет утечка памяти каждый раз при вызове этого метода.

Объект NSURL url не очень большой, поэтому возможно, что мы никогда не заметим утечку, хотя его все равно нужно очистить, нет никаких причин для его утечки.

Объект NSData data является png-изображением и может быть очень большим; мы каждый раз вызываем весь размер объекта каждый раз, когда этот метод вызывается. Представьте, что это вызывалось каждый раз, когда была нарисована ячейка таблицы: это не займет много времени, чтобы разрушить все приложение.

Итак, что нам нужно сделать, чтобы исправить проблемы? Это довольно просто, нам просто нужно освободить объекты, как только они нам больше не нужны, обычно сразу после последнего использования:

-(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 safe to leave the context
}

Некоторые люди предпочитают выпускать все сразу, прямо перед тем, как контекст закрывается в конце метода. В этом случае как [url release];, так и [data release]; появятся перед закрывающей скобкой }. Я нахожу, что если я выпущу их, как только я смогу, код станет яснее, что станет ясно, когда я перейду к нему позже, когда я закончил с объектами.

Подводя итог: мы создаем объекты, созданные с помощью alloc, new, copy или retain в вызовах метода, поэтому должны освобождать их до окончания контекста. Мы не владеем чем-то другим и никогда не должны отпускать их.


¹ Нет ничего действительно волшебного в четырех словах, это просто последовательное напоминание людей в Apple, которые создали методы, о которых идет речь. Если мы создадим собственные методы инициализации или копирования для собственного класса, то включение слов, их копирование или сохранение в соответствующих методах является нашей ответственностью, и если мы не будем использовать их в наших именах, тогда мы 'нужно будет запомнить для себя, прошла ли собственность.

Ответ 3

Одна вещь, которая помогает мне много, - иметь точку останова на objc_exception_throw. В любое время, когда я собираюсь получить исключение, я ударил эту точку останова, и я могу отлаживать резервную копию цепочки стека. Я просто оставляю эту точку останова включенной в моих проектах iPhone.

Для этого в xcode перейдите в нижнюю часть левой панели "Группы и файлы" и найдите "Точки останова". Откройте его и нажмите "Контрольные точки проекта", а в области подробностей (вверху) вы увидите синее поле с надписью "Двойной щелчок по символу". Дважды щелкните по нему и введите "objc_exception_throw".

В следующий раз, когда вы создадите исключение, вы остановитесь и в отладчике вы можете вернуться к цепочке стеков к вашему коду, вызвавшему исключение.

Ответ 4

Определенно дайте правилам управления памятью быстрый обзор. Ничто не выпрыгивает, что приведет к ошибке, которую вы получаете, но вы пропускаете все те объекты, которые вы выделяете. Если вы не понимаете шаблон сохранения/выпуска, есть вероятность, что в вашем коде есть еще одно место, где вы не сохраняете объект должным образом, и что вызывает ошибку EXC_BAD_ACCESS.

Также обратите внимание, что в NSString есть методы для работы с путями файловой системы, вам никогда не придется беспокоиться о разделителе самостоятельно.

Ответ 5

В общем, если вы получаете EXC_BAD_ACCESSs в своем коде, и вы не можете на всю жизнь выяснить, почему, попробуйте использовать NSZombie (нет, я не шучу).

В Xcode разверните раздел "Исполняемые файлы" слева. Дважды щелкните список, который имеет то же имя, что и ваш проект (он должен быть единственным). В появившемся окне перейдите в раздел "Аргументы", а в нижней части нажмите кнопку "плюс". Имя должно быть NSZombieEnabled, а значение должно быть равно YES

Таким образом, когда вы пытаетесь получить доступ к выпущенному объекту, у вас будет больше того, что вы делаете. Просто установите значение NO, если вы выяснили ошибку.

Надеюсь, это поможет кому-то!

Ответ 6

Эти ошибки возникают, когда вы неправильно управляете памятью (т.е. объект высвобождается преждевременно или аналогично)

Попробуйте сделать что-то вроде следующего.

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

Я потратил много времени на эксперименты, пытаясь понять концепции выпуска/авторекламы. Иногда нужно также воспроизводить ключевое слово сохранения (хотя, вероятно, и не в этом случае)

Другим вариантом может быть просто путь не существует или не может быть прочитан?

Ответ 7

Возможно, initWithContentsOfFile не принимает аргумент пути? Просмотрите различные методы init для UIImage, я думаю, что для принятия пути существует другой.

Возможно, что-то интересное, что вы должны сделать для создания пути? Я помню, что я что-то делал с "пучками"? Извините, что я так расплывчата, это все, что я помню небрежно.

Ответ 8

удалите слэш из пути и убедитесь, что он находится в проекте. не имеет значения, находится ли он в этом каталоге, но он должен быть добавлен в проект для доступа к нему.