Как обойти / обработать делегирование ошибок EXC_BAD_ACCESS? Obj C
Я кодирую библиотеку (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: метод, который, вероятно, является правильным шаблоном кодирования, но кажется неправильным, что клиент должен быть настолько точным в обработке моих объектов.
Что меня действительно беспокоит, так это то, что я, как разработчик библиотеки, еще не нашел способа "вставить" мой код, чтобы он не выдавал исключение для пользователя, если он не выполняет только правильные вещи. По сути, я хотел бы получить свой объект:
- Получить запрос.
- Иди ищи ответ.
- Найти ответ.
- Попробуйте вернуть ответ.
- Поймите, что на другом конце ничего нет.
- Сдайся и умри сам по себе. (ОК, так что "умереть самому", вероятно, не произойдет, но вы понимаете, в чем дело)
Один интересный момент:
Основная причина, по которой я могу предотвратить возникновение ошибок такого типа, заключается в следующем:
Я сделал следующие шаги:
- Встроенные файлы моей библиотеки.h/.m.
- Сгенерировал выходной файл.a моей библиотеки.
- Импортировал файлы моей библиотеки.a/.h в другой проект.
- Была ошибка, описанная выше.
- Нужно просмотреть код из одного из файлов.m, которые ДОЛЖНЫ быть скрыты внутри файла.a.
Я что-то здесь упускаю? Действительно ли я рискую раскрыть весь мой исходный код, если он когда-нибудь выдаст ошибку для клиента? (Это всего лишь побочный вопрос, но я довольно обеспокоен здесь!)
Спасибо за любую помощь, вы можете помочь мне стать лучшим программистом!
---РЕДАКТИРОВАТЬ---
Я нашел другую причину, почему это важно. В другом контроллере представления, где я использую эту библиотеку, я реализовал стратегию NSTimer. Если представление извлекается из стека навигации (т. Е. В viewWillDisappear:animated: метод), я делаю недействительным указанный таймер. Таким образом, больше не будет звонков в мою библиотеку после исчезновения представления.
Вот в чем проблема: что, если представление исчезнет в середине продолжительного вызова? Да, это сложно и маловероятно, но я просто произвел это на симуляторе. В частности, именно поэтому я ищу обходной путь, который позволит моему коду реализовать "эй, на другом конце этого канала ничего нет", а затем изящно провалиться. Кто-нибудь?
Спасибо!
3 ответа
Есть несколько подходов к этой проблеме:
Традиционный делегатский подход (
UITableViewDelegate
) обязывает очистить себя как делегата перед уходом. Это традиционно делается вdealloc
делегата сotherObject.delegate = nil
, Невыполнение этого требования является ошибкой программирования. Это в основном то, что вы видите. Это общая схема, когда срок службы делегата и делегата в основном одинаков.Другой подход заключается в том, как
NSURLConnection
обрабатывает это: сохраняйте своего делегата, пока вы не закончите. Ключ к этому хорошо работает в том, чтоNSURLConnection
имеет собственный срок службы, поэтому цикл сохранения будет работать автоматически.UITableView
не может сохранить свой делегат, потому что это почти всегда создает постоянный цикл сохранения. Если ваш объект живет некоторое время, а затем уходит, то это имеет смысл. Как правило, здесь делегат имеет более короткий срок службы, чем делегат, поэтому цикл сохранения ничего не повредит.
Любой объект, который вызывает performSelector:withObject:afterDelay:
должен всегда звонить cancelPreviousPerformRequestsWithTarget:self
в своем собственном dealloc
, Это не имеет ничего общего с вашим делегатом. Он должен быть самодостаточным для самого объекта. (Я не знаю, почему я продолжаю думать, что это правда, а потом еще раз доказываю себе, что это не так. Когда вы вызываете executeSelector:...afterDelay:, вы удерживаете, поэтому вы не можете освободить его до того, как он сработает Мое Боковое примечание, хотя и верно, здесь не имеет значения.)
ПРИМЕЧАНИЕ cancelPrevious...
действительно дорогой в моем опыте. Если вам нужно позвонить cancelPrvious...
очень часто, я рекомендую держать свой собственный один выстрел NSTimer
и просто сбрасывать его, когда он срабатывает, чтобы получить тот же эффект. performSelector:withObject:afterDelay:
это просто обертка вокруг таймера.
Я отвечаю сам, потому что страница предупредила меня, чтобы в комментариях не было расширенных обсуждений...:)
Итак, похоже, что часть моего ответа [self performSelector:withObject:afterDelay:]
автоматически сохраняет мой объект до тех пор, пока он не "выстрелит этим выстрелом", и в этот момент я предполагаю, что контроллер представления умирает.
Итак, теперь имеет смысл, почему мой пользовательский класс пытается получить доступ к освобожденному объекту, когда он пытается вернуть свой ответ своему делегату, который является объектом __unsafe_unretained, то есть он может умереть по своему усмотрению (я думаю).
То, что я хотел бы сейчас, - это способ предотвратить возникновение ошибки. В.NET у меня есть всевозможные варианты обработки ошибок для этого, но я не могу думать об отказоустойчивом "спасении" здесь.
я пробовал [[self Delegate] isKindOfClass:...
, но не могу быть уверен, какой класс будет делегатом, поэтому он не будет работать.
Я также пытался [[self Delegate] respondsToSelector:@selector(...)]
, Я не уверен, почему это не удается, но здесь я также получаю EXC_BAD_ACCESS.
Чего я не хочу, так это чтобы мои клиенты могли испортить мой продукт с такой простой, невинной ошибкой.
Кроме того, кто-нибудь знает, почему такой сбой дает мне такой легкий доступ к содержимому файла.m, который должен быть скрыт внутри моего файла.a? Я неправильно построил свою библиотеку?
Спасибо!
Попробуйте установить делегатов в ноль в dealloc.
пример:
self.fetchedResultsController.delegate = nil;
В последнее время я много видел эту проблему и обычно ее решаю. Хотя предполагается, что делегаты являются слабыми ссылками, иногда некоторые частные реализации также используют их.
Если я освобождаю, я получаю плохой доступ, если я сохраняю, я пропускаю
Вот где у меня была похожая проблема.
Редактировать: при использовании ARC вы все равно можете переопределить dealloc для очистки, вы просто не можете вызвать [super dealloc] или освободить что-либо.