【问题标题】:Memory leak on reading images from document directory从文档目录读取图像时内存泄漏
【发布时间】:2012-07-06 09:21:29
【问题描述】:

我正在循环读取文档目录中的图像。

for(iArrCount=0;iArrCount<[arrImages count];iArrCount++)
    {
UIButton *btnImage = [UIButton buttonWithType:UIButtonTypeCustom];
            btnImage.frame = CGRectMake(xRow, yRow, width, height);
            [btnImage addTarget:self action:@selector(imageDetails:) forControlEvents:UIControlEventTouchUpInside];
            btnImage.tag = iCount;
            if(iCount<[arrImages count])
            {


                NSString *workSpacePath=[[self applicationDocumentsDirectory] stringByAppendingPathComponent:[arrImages objectAtIndex:iCount]];
                [btnImage setBackgroundImage:[UIImage imageWithData:[NSData dataWithContentsOfFile:workSpacePath]] forState:UIControlStateNormal];
                [scrollImages addSubview:btnImage];
}
}  

这样做时,控制权转到didRecieveMemoryWarning 并且应用程序崩溃。如果我用资源文件夹中的图像替换图像,应用程序不会崩溃。为什么这样?

【问题讨论】:

    标签: iphone


    【解决方案1】:

    问题在于您的按钮保持对全分辨率图像的引用。您需要做的是将图像按比例缩小到按钮尺寸,并将按比例缩小的图像设置为按钮背景。像这样的东西应该可以解决问题:

    在 UIImage 上创建一个类别...我们称之为 UIImage+Scaler.h

    // UIImage+Scaler.h
    #import <UIKit/UIKit.h>
    
    @interface UIImage (Scaler)
    - (UIImage*)scaleToSize:(CGSize)size;
    @end
    

    以及实现:

    // UIImage+Scaler.m
    #import "UIImage+Scaler.h"
    
    #define kBitsPerComponent 8
    #define kBitmapInfo       kCGImageAlphaPremultipliedLast
    
    - (UIImage*)scaleToSize:(CGSize)size
    {
        CGBitmapInfo bitmapInfo = kBitmapInfo;
        size_t bytesPerRow = size.width * 4.0;
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = CGBitmapContextCreate(NULL, size.width, 
                                       size.height, kBitsPerComponent, 
                                       bytesPerRow, colorSpace, bitmapInfo);
    
        CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
        CGContextDrawImage(context, rect, self.CGImage);
    
        CGImageRef scaledImageRef = CGBitmapContextCreateImage(context);
        UIImage* scaledImage = [UIImage imageWithCGImage:scaledImageRef];
    
        CGImageRelease(scaledImageRef);
        CGContextRelease(context);
        CGColorSpaceRelease(colorSpace);
    
        return scaledImage;
    }
    

    好的,现在回到您的代码。就像其他海报所说的那样,您需要一个自动释放池。

    CGSize buttonSize = CGSizeMake(width, height);
    
    for(iArrCount=0;iArrCount<[arrImages count];iArrCount++)
    {
        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
        UIButton *btnImage = [UIButton buttonWithType:UIButtonTypeCustom];
        btnImage.frame = CGRectMake(xRow, yRow, width, height);
        [btnImage addTarget:self 
                     action:@selector(imageDetails:)      
           forControlEvents:UIControlEventTouchUpInside];
        btnImage.tag = iCount;
        if(iCount<[arrImages count])
        {
            NSString *workSpacePath=[[self applicationDocumentsDirectory]  
                           stringByAppendingPathComponent:
                                [arrImages objectAtIndex:iCount]];
            UIImage* bigImage = [UIImage imageWithData:[NSData   
                                 dataWithContentsOfFile:workSpacePath]];
            UIImage* smallImage = [bigImage scaleToSize:buttonSize];
            [btnImage setBackgroundImage:smallImage forState:UIControlStateNormal];
            [scrollImages addSubview:btnImage];
        }
        [pool drain]; // must drain pool inside loop to release bigImage
    } 
    

    希望这能解决问题。

    【讨论】:

    • 您也可以使用 dispatch_apply 代替 for 循环,并从主队列中获得所有这些工作。然后,当将按钮添加到滚动视图时,通过主队列调度该方法。这样我们就不会在加载图像时阻塞主线程。
    • @timthetoolman 好建议!您还可以缓存按比例缩小的图像(持久地),并且只为新图像计算它们......但是由于 OP 正在努力解决一些基本的内存管理问题,因此最好不要使事情过于复杂;-)
    【解决方案2】:

    将您的 for 循环放入自动释放池中:

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    for(...)
    {
       ....
    }
    [pool release];
    

    【讨论】:

    • 仍然崩溃。我发现了一件事。我注释掉了 [btn setBackgroundImage] 行并分配了一个资源图像。但是我没有注释掉从文档目录中读取文件的行。应用没有崩溃。
    • 检查[NSData dataWithContentsOfFile:workSpacePath]发生了什么
    【解决方案3】:

    使用NSAutoreleasePool。将您的代码保存在自动释放池中。

    for(iArrCount=0;iArrCount<[arrImages count];iArrCount++)
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
        ......
        ......
        [pool drain];
    }
    

    【讨论】:

    • 仍然崩溃。我发现了一件事。我注释掉了 [btn setBackgroundImage] 行并分配了一个资源图像。但是我没有注释掉从文档目录中读取文件的行。应用没有崩溃。
    • 嗯,看起来图像很大,因此需要很大的内存。这就是它崩溃的原因。我认为您应该重新使用按钮,就像我们正在重新使用表格单元一样。
    猜你喜欢
    • 2012-11-19
    • 2017-01-24
    • 1970-01-01
    • 1970-01-01
    • 2016-08-10
    • 2018-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多