RespondsToSelector отправить в освобожденный объект
Я пытаюсь выяснить, почему происходит сбой моего приложения (RSS Reader), если я отправляю неправильный URL в NSXML Parser. Я получил EXC_BAD_ACCES
С. Итак, после некоторого поиска я обнаружил, что должен использовать зомби. Поэтому я добавил следующие аргументы в среду:
CFZombieLevel = 3
NSMallocStaclLogging = YES
NSDeallocateZombies = NO
MallocStackLoggingNoCompact = YES
NSZombieEnabled = YES
NSDebugEnabled = YES
NSAutoreleaseFreedObjectCheckEnabled = YES
Я также добавил malloc_error_break
как точка останова. Затем я добавил несколько других точек останова в GUI и нажал "Build and Debug". В консоли я получаю следующее сообщение:
2010-08-28 18:41:49.761 RssReader[2850:207] *** -[XMLParser respondsToSelector:]: message sent to deallocated instance 0x59708e0
Иногда я также получаю следующее сообщение:wait_fences: failed to receive reply: 10004003
Если я наберу "shell malloc_history 2850 0x59708e0", я получу следующее:
...
ALLOC 0x5970870-0x59709d7 [size=360]: thread_a0aaa500 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | -[UIApplication handleEvent:withNewEvent:] | -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] | -[UIApplication
...
----
FREE 0x5970870-0x59709d7 [size=360]: thread_a0aaa500 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | -[UIApplication handleEvent:withNewEvent:] | -[UIApplication
...
ALLOC 0x59708e0-0x597090f [size=48]: thread_a0aaa500 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | -[UIApplication handleEvent:withNewEvent:] | -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] | -[UIApplication
...
Binary Images:
0x1000 - 0x6ff3 +RssReader ??? (???) <6EBB16BC-2BCE-CA3E-C76E-F0B078995E2D> /Users/svp/Library/Application Support/iPhone Simulator/4.0.1/Applications/AF4CE7CA-88B6-44D4-92A1-F634DE7B9072/RssReader.app/RssReader
0xe000 - 0x1cfff3 +Foundation 751.32.0 (compatibility 300.0.0) <18F9E1F7-27C6-2B64-5B9D-BAD16EE5227A>
...
Что это значит? Как мне узнать, какой объект 0x59708e0? Я не могу найти код, который вызывает сбой моего приложения. Единственное, что я знаю, это то, что это должно быть сообщение RespondsToSelector. Я добавил точку останова ко всем моим сообщениям RespondsToSelector. Они получают удар, но приложение не падает в этот момент. Я также попытался закомментировать их, за исключением одного, а также приложение выходит из строя. Тот, который не был закомментирован, не попал. Где у меня утечка памяти?
Следующая непонятная вещь заключается в том, что NSXML Parser продолжает свою работу, несмотря на то, что вызывается делегат parseErrorOccurn. После двух раз выдается ошибка, приложение вылетает.
Почему зомби в бегах с Peformance Tool отключены?
Редактировать:
Теперь я использовал эту инструкцию (не могу опубликовать. Извините. Защита от спама) Я получил это работает. Вот результат: http://yfrog.com/mrzombievp Так в чем смысл этого?
@Graham: в моем классе парсера я создаю экземпляр NSXMLParser
:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
...
NSXMLParser *rssParser = [[NSXMLParser alloc] initWithData:responseData];
[rssParser setDelegate:self];
...
[rssParser parse];
//[rssParser release];
}
Во время поиска ошибки я закомментировал метод выпуска. В настоящее время rssParser никогда не выпускается в классе анализатора.
В моем RootViewController
класс я создаю экземпляр моего парсера:
- (void)loadData {
if (newsItems == nil) {
[activityIndicator startAnimating];
XMLParser *rssParser = [[XMLParser alloc] init];
[rssParser parseRssFeed:@"http://feeds2.feedburner.com/TheMdnShowtest" withDelegate:self];
[rssParser release];
rssParser = nil;
} else {
[self.tableView reloadData];
}
}
Если я не выпущу его здесь, он не рухнет. Но для каждого выделенного я должен сделать релиз? Или я должен автоматически выпустить NSXMLParser
в connectionDidFinishLoading
?
4 ответа
В RootViewController.h я объявил свойство rssParser:
@class XMLParser;
@interface RootViewController : UITableViewController {
...
XMLParser *rssParser;
}
...
@property (retain, nonatomic) XMLParser *rssParser;
@end
В RootViewController.m у меня есть метод с именем errorOccurred:
- (void)errorOccurred {
[rssParser release];
rssParser = nil;
if ([activityIndicator isAnimating]) {
[activityIndicator stopAnimating];
}
}
В моем файле XMLParser.m я вызываю ошибку, которая произошла два раза:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
...
if ([_delegate respondsToSelector:@selector(errorOccurred)])
[_delegate errorOccurred];
else
{
[NSException raise:NSInternalInconsistencyException
format:@"Delegate doesn't respond to errorOccurred:"];
}
}
Чтобы увидеть, как объявляется _delegate, посмотрите учебник http://www.cocoadevblog.com/iphone-tutorial-creating-a-rss-feed-reader. Это переменная id и имеет собственный метод установки и получения (я думаю, вы также можете объявить ее как свойство). Второй раз:
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
...
if ([_delegate respondsToSelector:@selector(errorOccurred)])
[_delegate errorOccurred];
else
{
[NSException raise:NSInternalInconsistencyException
format:@"Delegate doesn't respond to errorOccurred:"];
}
}
Выпуск моих переменных rssParser выглядит следующим образом:
В loadData в RootViewController.m я никогда не выпускаю его. К сожалению, это произойдет сбой, если я сделаю это в loadData. Он освобождается только в случае возникновения ошибки (см. Выше) или в методе dealloc. Но я думаю, что это должно работать нормально, поскольку оно объявлено как собственность.
- (void)loadData {
if (newsItems == nil) {
[activityIndicator startAnimating];
self.rssParser = [[XMLParser alloc] init];
[rssParser parseRssFeed:@"http://www.wrongurl.com/wrongrss.xml" withDelegate:self];
} else {
[self.tableView reloadData];
}
}
В XMLParser.m я выпускаю его после метода разбора:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
...
NSXMLParser *rssParser = [[NSXMLParser alloc] initWithData:responseData];
[rssParser setDelegate:self];
[rssParser parse];
[rssParser release];
rssParser = nil;
}
Обратите внимание, что имена двух переменных одинаковы (rssParser), но они разные. В RootViewController я создаю экземпляр XMLParser, а в XMLParser.m я создаю экземпляр NSXMLParser.
Поэтому я думаю, что на этом я остановлюсь до тех пор, пока у меня не появится новая ошибка или кто-то из вас не объяснит мне, почему это плохо.
Зомби отключен, поскольку вы используете его с утечками памяти, поскольку все зомби будут сигнализироваться как утечки. Чтобы запустить инструмент "Зомби", вы можете перейти в меню "Инструменты", выбрать "Файл"> "Создать" и выбрать только инструмент "Зомби". При этом программа остановится, если зомби получит сообщение, и в небольшом всплывающем окне появится ссылка. к этому объекту зомби и его истории
У вас также есть rssParser, та же самая переменная экземпляра, которую вы настроили выпустить здесь...
- (void)errorOccurred {
[rssParser release];
rssParser = nil;
if ([activityIndicator isAnimating]) {
[activityIndicator stopAnimating];
}
}
... выпущен в вашем методе dealloc? Это приведет к двойному выпуску и, таким образом, EXEC_BAD_ACCESS.
Где-то вы размещаете XMLParser. Давайте посмотрим этот код. Вы же не выпускаете его автоматически?
Где-то это освобождается... это присвоено собственности? Давайте посмотрим, что определение свойства.
Позже будет вызываться responsedsToSelector: метод, но это может быть любой метод. Дело в том, что ваш XMLParser был выпущен раньше, чем вы хотели.