0

我对 iphone 开发很陌生,我的应用程序遇到了奇怪的崩溃。事实上,我的应用程序总是在模拟内存警告后崩溃。我每次都可以重现这种行为,并设法隔离了故障线路:)。

我在自定义 UITableViewController 中工作,提供自定义 UITableViewCells。

@implementation CustomTableViewController
// [...]
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{   
static NSString *CellIdentifier = @"MyTableViewCell";
UITableViewCell *cell = nil;

if ([indexPath row] < [dataList childCount])
{
    cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];

    if (nil == cell) 
    {
        cell = [[[KpowUITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                          reuseIdentifier:CellIdentifier] autorelease];

        KUICustomView* customView = [[KUICustomView alloc]initWithFrame:CGRectZero];
        [(KpowUITableViewCell*)cell setFrontView:customView];
        [customView release];
    }

    KUICustomView* cView = [(KpowUITableViewCell*)cell frontView];
    [cView setDataObject:[dataList getChildAtIndex:[indexPath row]]]; // The crash happens in this function
}
// [...]

这是我为单元格视图设置自定义数据对象的函数:

-(void)setDataObject:(DataObject *)do
{
    [do retain];
    [dataObject release];
    dataObject = do;

    NSString* defaultPath = [NSString stringWithFormat:@"%@/default_image.png", [[NSBundle mainBundle] resourcePath]];
    UIImage* defaultImage = [[UIImage alloc] initWithContentsOfFile:defaultPath];
    [self setImage: defaultImage];//[UIImage imageNamed:@"default_image"]]; // The crash happens in this function
    [defaultImage release];
    // [...]

最后,这就是魔法发生的地方:

-(void)setImage:(UIImage *)img
{
    [img retain];
    NSLog(@"setImage : old image > %@/%@/%i", [image description],         [[image class]description], [image retainCount]);
    [image release]; // CRASH EXC_BAD_ACCESS
    image = img;

    [self setNeedsDisplay];
}

因此,在正常情况下一切正常。但是,如果我模拟内存警告,滚动我的 UITableView 并调用所有这些函数,应用程序就会崩溃。如果我删除 [image release],则不会崩溃(但是'Hai there memory leaks')。NSLog 的输出总是类似于:

setImage : old image > <UIImage: 0x4b54910>/UIImage/1

我真的看不出我做错了什么,或者我可以做些什么来解决这个问题。这是 Xcode 调试器的屏幕截图...

http://img30.imageshack.us/i/debuggerscreen.png/

欢迎任何帮助。提前致谢

编辑 1: @bbum Build and Analyze 向我展示了一些不相关的警告,但仍然有用。甚至没有看到它在那里

我在另一个地方设置了图像。在 中setDataObject,图像只是一个占位符。我异步启动了真实图像的下载,然后将其恢复为requestDidFinishLoad. 方法是这样的:

- (void)requestDidFinishLoad:(KURLRequest*)request
{
    if (request == currentRequest) 
    {
        UIImage* img = [[UIImage alloc] initWithData:[request data]];

        if (nil != img)
            [self setImage:img];

        [img release];
    }

    if (currentRequest == request)
        currentRequest = nil;
    [request release];
}

我用 NSZombie Detection 运行了仪器,结果似乎指向了另一个方向。这是一个屏幕截图:

http://img13.imageshack.us/i/zombieinstrument.jpg/

我不太确定该怎么做,但调查正在进行中:)

4

5 回答 5

4
[image release]; // CRASH EXC_BAD_ACCESS

您的回溯表明您deallocUIImage. 你在某个地方过度释放了image

首先,尝试“构建和分析”,看看它是否会发出任何有用的警告。修复它们。

接下来,打开 Zombie Detection 尝试重现问题。它可能会提供线索。

请注意,@property声明 setter/getter 方法对(或其中之一)“仅仅是”方便。 [foo setImage:bar]无论您@property是直接使用还是声明该方法,都完全等效。同样foo.image = bar;是完全一样的[foo setImage:bar];

最后,这就是处理你的所有image代码吗?你如何处理低内存警告?

此外,如果你的 setter没有调用setNeedsDisplay:. 使用简单@property@synthesizesetter/getter。然后,当你调用setter时,调用setNeedsDisplay:之后就行了。这使设置 UI 的业务与确定何时需要显示无关。


啊哈!你的僵尸东西非常有用。特别是,您似乎过早地释放了 URLConnection,这会导致 NSData 过早地释放。这很容易成为问题的根源,或者至少应该在尝试解决问题之前解决。

于 2011-02-16T16:43:27.653 回答
1

尤里卡!我终于发现我做错了什么。当图像被异步加载时,它使用来自用于缓存的自定义对象的数据,存储在缓存管理器中。当发出内存警告时,缓存管理器释放所有内容,从内存中销毁缓存对象。这是我的 dealloc 在我的“可缓存对象”中的样子:

-(void)dealloc
{
    // [...]
    [data dealloc];
    // [...]
}

是的,我明确地调用了 dealloc ......所以当 UIImage 想要释放它自己的数据指针时,它当然失败了......

我觉得好傻^^。(我总是很难调试自己的程序,因为我有时会认为部分代码是“OK”的,我什至不想看那里......)

底线: NSZombie对找出真正的罪魁祸首非常有用(感谢@bbum)。并且永远不要(?)显式调用 dealloc

(无论如何“关闭”这个问题?

于 2011-02-17T20:42:44.763 回答
0

您可能正在释放图像以响应内存警告,而没有将其设置为 nil。因此,当您调用时,setImage:您正在释放一个已经释放的对象,因此会崩溃。尝试类似:

- (void)viewDidUnload {
    [super viewDidUnload];
    [image release]; image = nil;
}

或者如果image被声明为属性

- (void)viewDidUnload {
    [super viewDidUnload];
    self.image = nil;
}
于 2011-02-16T08:23:55.800 回答
0

特别是为了解决@sabby 的误解:

-(void)setImage:(UIImage *)img
{
    [img retain]; // img rc +1
    image = img; 
    if(image)
    {
        [image release]; // img rc -1
    }
    // image set, but not retained by `self`
}

最终结果?不保留项集的 setter 方法。将其与:

- initWithImage:anImage
{
     if (self=[super init]) {
           image = [anImage retain];
     }
     return self;
}

上面的 setter 会泄漏传递给 的原始图像init,过度释放传递给的任何图像setImage:,如果应用程序存活足够长的时间,很可能会崩溃:

- (void) dealloc
{
    [image release];
    [super dealloc];
}
于 2011-02-17T17:53:04.363 回答
-1

这样做,对你有帮助吗

-(void)setImage:(UIImage *)img
{
    [img retain];
    NSLog(@"setImage : old image > %@/%@/%i", [image description],         [[image class]description], [image retainCount]);
     // CRASH EXC_BAD_ACCESS
    image = img;
if(image)
{
[image release];
}

    [self setNeedsDisplay];
}

我自己没有检查过,但是您崩溃的原因是图像的发布。

于 2011-02-16T05:46:42.497 回答