Как устранить ошибки EXC_BAD_ACCESS, возникшие при разработке iPhone

StackOverflow https://stackoverflow.com/questions/607222

Вопрос

Я пытаюсь сделать простую вещь;прочитать изображение из Интернета, сохранить его в каталоге документов приложения на 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];
}

Код не работает в операторе возврата, когда я пытаюсь инициализировать UIImage из файла.Есть идеи?

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

Это было полезно?

Решение

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

Рекомендую вам внимательно прочитать:

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

Другие советы

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

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

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

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

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

Давайте посмотрим на каждую строку кода ОП:

-(UIImage *) downloadImageToFile {

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

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

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

    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:слово выделять там сообщает use, что мы владеем объектом и что нам нужно будет освободить его самостоятельно.Если мы этого не сделаем, то в коде произойдет утечка памяти.

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

}

Метод завершен, поэтому контекст для переменных завершился.Глядя на код, мы видим, что мы владеем обоими 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's safe to leave the context
}

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

Другой вариант может заключаться в том, что путь просто не существует или его невозможно прочитать?

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top