【问题标题】:CGDataProviderCopyData builds up in memory causing crashCGDataProviderCopyData 在内存中堆积导致崩溃
【发布时间】:2013-08-28 19:25:53
【问题描述】:

好的,我正在从服务器上下载一堆大图像(5mb),然后将这些图像拼接在一起,并从一个字节数组中渲染整个图像。但是,我意识到每个图像的数据都没有被释放,因此会导致内存警告和我的应用程序崩溃。我认为由于我的显式 (__bridge_transfer NSData *) 强制转换,ARC 会负责释放对象,但事实证明它仍然是一个问题。在仪器中,大约 1mb 的名为“CGDataProviderCopyData”的对象会堆积起来,并且不会因为每个正在拼接到整个图像中的文件而被丢弃。有什么想法或任何人可以引导我朝着正确的方向前进吗?非常感谢。

 // Create  array to add all files into total image
NSMutableArray *byteArray = [[NSMutableArray alloc] initWithCapacity:(imageHeight * imageWidth)];

// Iterate through each file in files array
for (NSString *file in array)
{        
    // Set baseURL for individual file path
    NSString *baseURL = [NSString stringWithFormat:@"http://xx.225.xxx.xxx%@",[imageInfo objectForKey:@"BaseURL"]];

    // Specify imagePath by appending baseURL to file name
    NSString *imagePath = [NSString stringWithFormat:@"%@%@", baseURL, file];

    // Change NSString --> NSURL --> NSData
    NSURL *imageUrl = [NSURL URLWithString:imagePath];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];

    // Create image from imageData
    UIImage *image = [UIImage imageWithData:imageData];
    CGImageRef cgimage = image.CGImage;

    size_t width  = CGImageGetWidth(cgimage);
    size_t height = CGImageGetHeight(cgimage);

    size_t bpr = CGImageGetBytesPerRow(cgimage);
    size_t bpp = CGImageGetBitsPerPixel(cgimage);
    size_t bpc = CGImageGetBitsPerComponent(cgimage);
    size_t bytes_per_pixel = bpp / bpc;

    // Get CGDataProviderRef from cgimage
    CGDataProviderRef provider = CGImageGetDataProvider(cgimage);

    // This is the object that is not being released
    NSData *data = (__bridge_transfer NSData *)CGDataProviderCopyData(provider);      //Using (__bridge_transfer NSData *) casts the provider to type NSData and gives ownership to ARC, but still not discarded

    const UInt8 *bytes = (Byte *)[data bytes];

    // Log which file is currently being iterated through 
    NSLog(@"---Stitching png file to total image: %@", file);

    // Populate byte array with channel data from each pixel
    for(size_t row = 0; row < height; row++)
    {
        for(size_t col = 0; col < width; col++)
        {
            const UInt8* pixel =
            &bytes[row * bpr + col * bytes_per_pixel];

            for(unsigned short i = 0; i < 4; i+=4)
            {
                __unused unsigned short red = pixel[i];         // red channel - unused
                unsigned short green = pixel[i+1];              // green channel
                unsigned short blue = pixel[i+2];               // blue channel
                __unused unsigned short alpha = pixel[i+3];     // alpha channel - unused

                // Create dicom intensity value from intensity = [(g *250) + b]
                unsigned short dicomInt = ((green * 256) + blue);

                //Convert unsigned short intensity value to NSNumber so can store in array as object
                NSNumber *DICOMvalue = [NSNumber numberWithInt:dicomInt];

                // Add to image array (total image)
                [byteArray addObject:DICOMvalue];
            }
        }
    }
    data = nil;
}
return byteArray;

通过 Xcode 运行“分析”也不会显示任何明显的泄漏。

【问题讨论】:

    标签: ios objective-c memory-management memory-leaks cfdata


    【解决方案1】:

    我拿了这段代码,几乎是一字不差地做了一些调查。使用 CFDataRef/NSData,我能够看到您在 NSDatas 没有消失的情况下看到的问题,并且我能够通过将使用 NSData 的代码部分包装在 @autoreleasepool 范围内来解决它,就像这样:

     // Create  array to add all files into total image
    NSMutableArray *byteArray = [[NSMutableArray alloc] initWithCapacity:(imageHeight * imageWidth)];
    
    // Iterate through each file in files array
    for (NSString *file in array)
    {        
        // Set baseURL for individual file path
        NSString *baseURL = [NSString stringWithFormat:@"http://xx.225.xxx.xxx%@",[imageInfo objectForKey:@"BaseURL"]];
    
        // Specify imagePath by appending baseURL to file name
        NSString *imagePath = [NSString stringWithFormat:@"%@%@", baseURL, file];
    
        // Change NSString --> NSURL --> NSData
        NSURL *imageUrl = [NSURL URLWithString:imagePath];
        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    
        // Create image from imageData
        UIImage *image = [UIImage imageWithData:imageData];
        CGImageRef cgimage = image.CGImage;
    
        size_t width  = CGImageGetWidth(cgimage);
        size_t height = CGImageGetHeight(cgimage);
    
        size_t bpr = CGImageGetBytesPerRow(cgimage);
        size_t bpp = CGImageGetBitsPerPixel(cgimage);
        size_t bpc = CGImageGetBitsPerComponent(cgimage);
        size_t bytes_per_pixel = bpp / bpc;
    
        // Get CGDataProviderRef from cgimage
        CGDataProviderRef provider = CGImageGetDataProvider(cgimage);
    
        @autoreleasepool
        {
            // This is the object that is not being released
            NSData *data = (__bridge_transfer NSData *)CGDataProviderCopyData(provider);      //Using (__bridge_transfer NSData *) casts the provider to type NSData and gives ownership to ARC, but still not discarded
    
            const UInt8 *bytes = (Byte *)[data bytes];
    
            // Log which file is currently being iterated through 
            NSLog(@"---Stitching png file to total image: %@", file);
    
            // Populate byte array with channel data from each pixel
            for(size_t row = 0; row < height; row++)
            {
                for(size_t col = 0; col < width; col++)
                {
                    const UInt8* pixel =
                    &bytes[row * bpr + col * bytes_per_pixel];
    
                    for(unsigned short i = 0; i < 4; i+=4)
                    {
                        __unused unsigned short red = pixel[i];         // red channel - unused
                        unsigned short green = pixel[i+1];              // green channel
                        unsigned short blue = pixel[i+2];               // blue channel
                        __unused unsigned short alpha = pixel[i+3];     // alpha channel - unused
    
                        // Create dicom intensity value from intensity = [(g *250) + b]
                        unsigned short dicomInt = ((green * 256) + blue);
    
                        //Convert unsigned short intensity value to NSNumber so can store in array as object
                        NSNumber *DICOMvalue = [NSNumber numberWithInt:dicomInt];
    
                        // Add to image array (total image)
                        [byteArray addObject:DICOMvalue];
                    }
                }
            }
            data = nil;
        }
    }
    return byteArray;
    

    在添加 @autoreleasepool 之后,我注释掉了您创建 NSNumber 并将它们放入数组中的部分,我能够在 Instruments 的 Allocations 模板中看到确实 CFData现在,随着循环的每一轮,物体都会被释放。

    我注释掉创建 NSNumbers 并将它们放入数组的部分的原因是,使用那里的代码,你最终将添加 width * height * 4 NSNumbers 到 byteArray。这意味着即使 NSData 被正确释放,您的堆使用量也会增加width * height * 4 * &lt;at least 4 bytes, maybe more&gt; 无论如何。也许这就是你需要做的,但这确实让我更难看到 NSDatas 发生了什么,因为它们的大小与 NSNumbers 数组相比相形见绌。

    希望对您有所帮助。

    【讨论】:

    • 我尝试用 autorelease 包装 for 循环,但仍然没有释放数据对象
    • @user2525045 经过更多调查编辑
    • 不确定我是否理解您关于堆增长的评论的最后一部分,无论如何。你能澄清一下吗?
    • 这里的代码导致内存被分配并以比 NSData 对象未被释放时更大的速度增长。换句话说,如果在内存中同时拥有 NSData 对象就足以导致您的应用程序被杀死,那么即使在它们被正确释放之后,您仍然会被杀死,因为您正在通过分析创建另一个数据结构比 NSDatas 本身还要大的图像。
    猜你喜欢
    • 2014-11-22
    • 2016-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-17
    • 2016-04-06
    • 2013-11-08
    • 1970-01-01
    相关资源
    最近更新 更多