Как обойти / обработать ошибки делегирования EXC_BAD_ACCESS? Obj C - PullRequest
4 голосов
/ 13 июля 2011

Я кодирую библиотеку (Obj-C для iPhone), которую я хочу упаковать и продать, поэтому мне, очевидно, нужно проработать любые дизайнерские изгибы, прежде чем выставлять их на продажу.Я также использую эту библиотеку, чтобы помочь мне разработать другое приложение.

Моя библиотека в значительной степени построена на делегировании задач.Основная функция, которую я имею, состоит в том, чтобы запустить (потенциально) длительный процесс, и когда он закончится, я вызываю метод Protocol делегата в делегате класса.

Дополнительным усложняющим фактором здесь является то, что я часто планирую запускать эту задачу каждые 30 секунд или около того.Обычно я делаю это с помощью [self executeSelector: @selector (someMethod :) withObject: nil afterDelay: 30], а не с помощью NSTimer.Затем, когда метод делегата успешно возвращается, я обрабатываю возвращенные данные и запускаю метод через 30 секунд.Это дает мне 30 секунд между вызовами метода, а не 30 секунд от начала одного вызова до следующего.(Это в основном на тот случай, если вызов когда-либо займет более 30 секунд, что не должно происходить.)

Ошибка, которую я улавливаю, заключается в том, что иногда метод обратного вызова Delegate завершается с ошибкой EXC_BAD_ACCESS,На основании моего расследования выяснилось, что представитель моей библиотеки классов исчез (был освобожден / освобожден) с тех пор, как начался длительный процесс.Таким образом, когда он вызывает [[self Delegate] doSomeDelegateMethod], он обращается к освобожденному объекту.

Я попытался сначала проверить [[self Delegate] responsedsToSelector: @selector (doSomeDelegateMethod)], но даже этот доступ, очевидно, также вызываетEXC_BAD_ACCESS.

Пока еще не кажется, что проверка [self Delegate] == nil также является правильным способом.

Один из способов, по-моему, я решил проблему,в этом конкретном случае, когда контроллер представления, который создает мой объект, исчезает (и, следовательно, на пути к свалке мусора), я вызываю [NSObject cancelPreviousPerformRequestsWithTarget: self].Это, видимо, решает проблему.(Означает ли это «исправление» также, что мой объект «знает» о предстоящем вызове и сохраняет себя в памяти до тех пор, пока не сможет успешно, отчаянно выстрелить своим последним выстрелом?)

Кажется, это ставит полосуна пулевой ране.Да, похоже, что это мешает моему приложению сломаться на этот раз, но моя интуиция говорит мне, что это плохое решение.

Я также рассмотрел возможность установки пользовательского объекта равным nil в моем viewWillDisappear: animated: method,что, вероятно, является правильным шаблоном кодирования, но кажется неправильным, что клиент должен быть настолько точным в обращении с моими объектами.

Что меня действительно беспокоит, так это то, что я, как разработчик библиотеки, еще не нашел способа «вставить» мой код, чтобы он не вызывал исключения для пользователя, если онине делай только правильные вещи.В принципе, я хотел бы получить мой объект:

  1. Получить запрос.
  2. Перейти искать ответ.
  3. Найти ответ.
  4. Попробуйте вернуть ответ.
  5. Поймите, что на другом конце ничего нет.
  6. Сдайтесь и умрите сами по себе.(Хорошо, так что «умереть самому», вероятно, не произойдет, но вы понимаете, в чем дело.)

Один интересный побочный момент:

Основная причина, по которой я могу предотвратитьЭтот тип ошибки происходит следующим образом:

Я сделал следующие шаги:

  1. Построил файлы моей библиотеки .h / .m.
  2. Сгенерировал свою библиотеку.выходной файл.
  3. Импортировал файлы моей библиотеки .a / .h в другой проект.
  4. Произошла ошибка, описанная выше.
  5. Получил возможность просмотреть код из одного изФайлы .m, которые СЛЕДУЕТ скрывать внутри файла .a.

Я что-то здесь упускаю?Действительно ли я рискую раскрыть весь свой исходный код, если он когда-нибудь выдаст ошибку для клиента?(Это всего лишь побочный вопрос, но я здесь весьма обеспокоен!)

Спасибо за любую помощь, которую вы можете оказать, чтобы помочь мне стать лучшим программистом!

--- EDIT ---

Я нашел другую причину, почему это важно. В другом контроллере представления, где я использую эту библиотеку, я реализовал стратегию NSTimer. Если представление извлекается из стека навигации (то есть в viewWillDisappear: animated: метод), я делаю недействительным указанный таймер. Таким образом, после исчезновения представления в мою библиотеку больше не будут поступать звонки.

Вот в чем проблема: что, если представление исчезнет В СРЕДНЕМ продолжительного вызова? Да, это сложно и маловероятно, но я просто произвел это на симуляторе. В частности, THIS - вот почему я ищу обходной путь, который позволит моему коду понять: «эй, на другом конце этого канала ничего нет», а затем изящно провалиться. Кто-нибудь?

Спасибо!

Ответы [ 3 ]

4 голосов
/ 13 июля 2011

Существует несколько подходов к этой проблеме:

  • Традиционный подход к делегату (UITableViewDelegate) требует, чтобы вы очистили себя как делегат, прежде чем уйти.Это традиционно делается в dealloc делегата с otherObject.delegate = nil.Невыполнение этого требования является ошибкой программирования.Это в основном то, что вы видите.Это общая схема, когда срок службы делегата и делегата в основном одинаков.

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

Любой объект, который вызывает performSelector:withObject:afterDelay:, всегда должен вызывать cancelPreviousPerformRequestsWithTarget:self в своем собственном dealloc.Это не имеет ничего общего с вашим делегатом.Он должен быть самодостаточным для самого объекта. (Я не знаю, почему я продолжаю думать, что это правда, а потом снова доказываю себе, что это не так. Когда вы вызываете executeSelector: ... afterDelay:, вы сохранены, поэтому вы не можете освободить его до того, как он сработает. Мое Боковое ПРИМЕЧАНИЕ, хотя и верно, здесь не имеет значения.)

Боковое ПРИМЕЧАНИЕ cancelPrevious... действительно дорого вмой опыт.Если вам приходится очень часто звонить cancelPrvious..., я рекомендую оставить свой собственный однократный NSTimer и просто сбросить его при срабатывании, чтобы получить тот же эффект.performSelector:withObject:afterDelay: - это просто обёртка вокруг таймера одного выстрела.

0 голосов
/ 13 июля 2011

Я отвечаю себе, потому что страница предупредила меня, чтобы в комментариях не было расширенных обсуждений ...:)

ОК, так что, похоже, часть моего ответа состоит в том, что [self performSelector:withObject:afterDelay:] автоматически сохраняет моиобъект до тех пор, пока не произойдет «выстрел», после чего я предполагаю, что контроллер представления умирает.

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

Что мне сейчас нужно, так это способ предотвратить возникновение ошибки.В .NET у меня есть все возможные варианты обработки ошибок, но я не могу думать об отказоустойчивом «спасении» здесь.

Я пытался [[self Delegate] isKindOfClass:...,но не могу быть уверен, какой класс будет делегатом, поэтому он не будет работать.

Я тоже пробовал [[self Delegate] respondsToSelector:@selector(...)].Я не уверен, почему это не помогает, но я также получаю здесь EXC_BAD_ACCESS.

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

Кроме того, кто-нибудь знает, почему такой сбой дает мне такой легкий доступ к содержимому файла .m, который должен быть скрыт внутри моего файла .a?Я неправильно построил свою библиотеку?

Спасибо!

0 голосов
/ 13 июля 2011

Попробуйте установить делегатов в ноль в dealloc.

пример:

self.fetchedResultsController.delegate = nil;

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

Если я выпускаю, я получаю плохой доступ, если я сохраняю, я пропускаю

Вот где у меня была похожая проблема.

Edit: при использовании ARC вы все равно можете переопределить dealloc для очистки, вы просто не можете вызвать [super dealloc] или освободить что-либо.

...