respondsToSelector 发送到释放的对象

Posted

技术标签:

【中文标题】respondsToSelector 发送到释放的对象【英文标题】:respondsToSelector send to deallocated object 【发布时间】:2011-04-05 05:44:42 【问题描述】:

如果我向 NSXML Parser 发送错误的 URL,我会尝试找出我的应用程序崩溃(RSS 阅读器)的原因。我有一个EXC_BAD_ACCESS。所以经过一番搜索后,我发现我必须使用 Zombies。所以我在环境中添加了以下参数:

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 继续其工作,尽管调用了 parseErrorOccurred 委托。抛出两次错误后,应用程序崩溃。

为什么在禁用性能工具的情况下运行中的僵尸?

编辑:

现在我使用了这个指令(无法发布。抱歉。垃圾邮件预防)我得到了这个工作。这是什么意思?

@格雷厄姆: 在我的解析器类中,我实例化了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];  
      

如果我不在这里释放它,它就不会崩溃。但是对于每个分配我都必须做一个释放?或者我应该在connectionDidFinishLoading 中自动释放NSXMLParser

【问题讨论】:

【参考方案1】:

当您将 Zombie 与 Memory Leaks 一起使用时,它会被禁用,因为所有 Zombie 都会被标记为泄漏。要运行 Zombie 工具,您可以转到 Instrument 菜单并执行 File>New 并单独选择 Zombie 工具,如果僵尸收到消息,程序将停止,您将在一个小弹出窗口中获得一个链接到那个僵尸对象和它的历史

【讨论】:

感谢您的提示!我在阅读corbinstreehouse.com/blog/2007/10/… 时意识到了这一点,我不知道 Instruments 是一个独立的程序,而不是 Xcode 的一部分。尽管如此,我还是不明白他们为什么提供那个菜单项,如果它不能使用的话。【参考方案2】:

您正在分配 XMLParser 的地方。让我们看看那个代码。你不会自动释放它吧?

它在某个地方被释放...它是否分配给了一个属性?让我们看看这个属性定义。

稍后将调用 respondsToSelector: 方法,但这可以是任何方法。关键是您的 XMLParser 在您预期之前就已经发布了。

【讨论】:

所以我编辑了我的问题。我正在使用 alloc,所以我认为我不会自动释放它。我不使用 RSS 解析器作为属性。什么时候发布合适? 看起来您的 XMLParser 类期待一些异步 I/O 响应?如果是这样,它需要挂起直到完成。因此你不能在 loadData 中释放它。在 connectionDidFinishLoading: 中自动释放本身是值得尝试的,如果那是它完成处理的时候。在任何连接错误处理方法中,您也必须这样做。 我没有看过同步/异步的东西。所以我告诉你我用的是什么。我使用 NSURLConnection 和 NSXMLParser。 NSURLConnection 正在创建异步连接,而 NSXMLParser 是一个 SAX 解析器。由于我在 connectionDidFinishLoading 中开始解析:解析发生在下载文件时(错误文件是 html 文件,5 秒后发生重定向)。该项目基于本教程:cocoadevblog.com/iphone-tutorial-creating-a-rss-feed-reader.【参考方案3】:

在 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 文件中,我调用 errorOccurred 两次:

- (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 变量,并且有自己的 setter 和 getter 方法(我认为您也可以将其声明为属性)。第二次:

- (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变量的释放如下:

在 RootViewController.m 的 loadData 中,我从不释放它。不幸的是,如果我在 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 的实例。

所以我想我会一直这样,直到我没有遇到新的错误或者你们中的某个人向我解释了为什么这很糟糕。

【讨论】:

【参考方案4】:

你也有 rssParser,你在这里设置要释放的同一个实例变量...

- (void)errorOccurred 
    [rssParser release];
    rssParser = nil;
    if ([activityIndicator isAnimating]) 
        [activityIndicator stopAnimating];
    

...在您的 dealloc 方法中发布?这将导致双重释放,从而导致 EXEC_BAD_ACCESS。

【讨论】:

好点!这个问题太老了,我不记得了。对不起。在我当前的项目中,errorOccured 不再发布。

以上是关于respondsToSelector 发送到释放的对象的主要内容,如果未能解决你的问题,请参考以下文章

[CustomViewController respondsToSelector:]:消息发送到释放的实例

带有自定义单元格子视图的 UITableView - respondsToSelector:]:消息发送到已释放的实例

-[ViewController respondsToSelector:]:消息发送到已释放实例 - 在 tableview 滚动

使用respondsToSelector:来发现对象是否响应消息

respondsToSelector的使用

respondsToSelector和 instancesRespondToSelector的理解