发送到已释放实例问题的消息

Posted

技术标签:

【中文标题】发送到已释放实例问题的消息【英文标题】:message sent to deallocated instance issue 【发布时间】:2011-05-29 01:46:53 【问题描述】:

过去几个小时我一直在调试以下问题,但似乎无法得出一个好的结论。

-[NSIndexPath row]:消息发送到deallocated instance 0x506cf90

我正在 UITableView 上的每个单元格上实现滑动识别器。每当检测到滑动时,都会调用下面的代码。它的作用是在单元格中显示/隐藏子视图。如果子视图已经显示,则隐藏,否则显示。如果单元格 5 上已经显示了子视图,并且在单元格 3 上检测到滑动,那么我们首先删除该子视图,然后在单元格 3 上添加子视图。

我尝试了下面的代码,它似乎适用于某些单元格。但是,特别是在 UITableView 底部有一个单元格,如果我尝试隐藏子视图,它会给我上面的错误。它对另一个单元格工作得很好,只是底部的单元格。这是为什么呢?

代码如下:

- (void)swipe:(UISwipeGestureRecognizer *)recognizer direction:(UISwipeGestureRecognizerDirection)direction

    if (recognizer && recognizer.state == UIGestureRecognizerStateEnded)
    
        // Get the table view cell where the swipe occured
        CGPoint location = [recognizer locationInView:self.table];
        NSIndexPath* indexPath = [self.table indexPathForRowAtPoint:location];
        ConvoreCell* cell = (ConvoreCell *) [self.table cellForRowAtIndexPath:indexPath];

        [self.table beginUpdates];

        NSLog(@"ROW is %d", global.row);
        //removing the options view at the other cell before adding a new one
        if (global != nil && global.row != indexPath.row)
            [sideSwipeView removeFromSuperview];
            [sideSwipeView release];
            sideSwipeView = nil;
        

        //options already exist, we need to remove it
        if (sideSwipeView != nil)
            [sideSwipeView removeFromSuperview];
            [sideSwipeView release];
            sideSwipeView = nil;
            slide = NO;
         else 
            //options do not exist and therefore we need to add it
            NSArray * buttonData = [[NSArray arrayWithObjects:
                                     [NSDictionary dictionaryWithObjectsAndKeys:@"Mark Read", @"title", @"mark.png", @"image", nil],
                                     [NSDictionary dictionaryWithObjectsAndKeys:@"Track", @"title", @"play.png", @"image", nil],
                                     [NSDictionary dictionaryWithObjectsAndKeys:@"Leave", @"title", @"delete.png", @"image", nil],
                                     nil] retain];

            NSMutableArray * buttons = [[NSMutableArray alloc] initWithCapacity:buttonData.count];
            sideSwipeView = [[UIView alloc] initWithFrame:CGRectMake(0, cell.frame.size.height-25, 320, 25)];
            [sideSwipeView setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin];
            [sideSwipeView setBackgroundColor:[UIColor colorWithPatternImage: [UIImage imageNamed:@"dotted-pattern.png"]]];
            [sideSwipeView setTag:-10];

            CGFloat leftEdge = BUTTON_LEFT_MARGIN;
            for (NSDictionary* buttonInfo in buttonData)
            
                if (!([[buttonInfo objectForKey:@"title"] isEqualToString:@"Mark Read"] && [[[groups objectAtIndex:indexPath.row] unread] intValue] == 0))
                

                    UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];

                    button.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

                    UIImage* buttonImage = [UIImage imageNamed:[buttonInfo objectForKey:@"image"]];
                    if ([[[groups objectAtIndex:indexPath.row] tracked] intValue] == 1 && [[buttonInfo objectForKey:@"title"] isEqualToString:@"Track"])
                        buttonImage = [UIImage imageNamed:@"pause.png"];
                        [button setSelected:YES];
                     else 
                        [button setSelected:NO];

                    button.frame = CGRectMake(leftEdge, 0, buttonImage.size.width, buttonImage.size.height);

                    UIImage* grayImage = [self imageFilledWith:[UIColor colorWithWhite:0.9 alpha:1.0] using:buttonImage];
                    [button setImage:grayImage forState:UIControlStateNormal];

                    if ([[buttonInfo objectForKey:@"title"] isEqualToString:@"Mark Read"])
                        [button addTarget:self action:@selector(markRead:) forControlEvents:UIControlEventTouchUpInside];
                     else if ([[buttonInfo objectForKey:@"title"] isEqualToString:@"Track"])
                        [button addTarget:self action:@selector(track:) forControlEvents:UIControlEventTouchUpInside];
                     else if ([[buttonInfo objectForKey:@"title"] isEqualToString:@"Leave"])
                        [button addTarget:self action:@selector(leave:) forControlEvents:UIControlEventTouchUpInside];
                    
                    [button setTag:indexPath.row];
                    [buttons addObject:button];

                    [sideSwipeView addSubview:button];
                    leftEdge = leftEdge + buttonImage.size.width + BUTTON_SPACING;
                
            

            [cell.contentView addSubview:sideSwipeView];
            [buttons release];
            [buttonData release];
            global = indexPath;
            slide = YES;

        
        [self.table endUpdates]; 
        [self.table deselectRowAtIndexPath:indexPath animated:YES];        

    

现在这里的问题是指向 indexPath 的指针不知何故被释放了。我能够通过制作 indexPath 的副本而不是仅仅引用指针来解决这个问题。所以我做了:

global = [indexPath copy];

由于某种原因,在调用此方法后,它会释放 indexPath,我不确定是谁做的。我认为 ios 做的...是真的吗?

【问题讨论】:

【参考方案1】:
    NSIndexPath* indexPath = [self.table indexPathForRowAtPoint:location];

返回一个自动释放的实例。所以,是的,您对global 的分配需要保留该对象。 copy 有效地返回一个保留的实例。

不过,不要泄漏。如果你这样做:

global = [indexPath copy];

而且你不先释放global的原始值,你会泄漏。

哦,还有:http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html

【讨论】:

indexPathForRowAtPoint: 不一定返回自动释放的实例(我不确定它是否真的返回)。它返回一个无主实例。这个错误在某些情况下会带来乐趣。 objectAtIndex: 然后 removeObjectAtIndex: 例如。 所以在我做副本之前我需要做全球发布? 为了安全起见,是的。否则,以前在全局中的任何内容都会泄漏 这对我来说似乎很奇怪,所以在复制之前全局内存的计数为 0,然后你释放它.. 然后它变为 -1.. 然后你复制它变为 0? Joshua 的迂腐是绝对正确的...... @adit 不要查看绝对保留计数。它是无用的,是一个实现细节(很可能,-1 表示“单例”)。

以上是关于发送到已释放实例问题的消息的主要内容,如果未能解决你的问题,请参考以下文章

[UIImageAsset 保留]:发送到已释放实例的消息

[UINavigationController 保留]:发送到已释放实例的消息

发送到已释放实例的消息

iOS:如何调试“发送到已释放实例的消息”

tableView:didSelectRowAtIndexPath - 发送到已释放实例的消息

发送到已释放实例 Core Data 的消息