【问题标题】:Memory Leak - UIImagePNGRepresentation内存泄漏 - UIImagePNGRepresentation
【发布时间】:2014-10-08 22:28:32
【问题描述】:

我正在尝试将图像从 UIImagePicker 复制到文档目录。我正在使用@"UIImagePickerControllerOriginalImage" 键从 UIImagePickerDelegate 的字典中获取原始图像。我正在使用UIImagePNGRepresentation 将图像写入文件。当我添加(重复该过程)高分辨率图像(图像大小约 20 mb)时,我遇到了内存问题。

我分析并使用了 Xcode 的内存泄漏功能,它放大了以下导致泄漏的代码。

@autoreleasepool {
    imagesData = UIImagePNGRepresentation(images);
    [imagesData writeToFile:name atomically:NO];
    imagesData = nil;
    //[UIImageJPEGRepresentation(images, 1.0) writeToFile:name atomically:YES];
}

我在这里看到了很多关于UIImagePNGRepresentation 引起的内存泄漏的问题。但是我还没有找到合适的解决方案来解决我的问题。需要帮助。

【问题讨论】:

    标签: ios iphone memory-management memory-leaks uiimage


    【解决方案1】:

    我不知道UIImagePNGRepresentation 有任何“泄漏”,但这肯定是对内存的过度使用,但这里有几个问题:

    1. 首先,通过UIImage 往返原始资产然后使用UIImagePNGRepresentation() 的过程相当低效,最终可能会得到比原始资产大得多的NSData。例如,我选择了一张照片,其原始资产为 1.5mb,UIImageJPEGRepresentationcompressionQuality 为 1.0)为 6mb,UIImagePNGRepresentation() 约为 10mb。 (这些数字在不同图像之间可能会发生很大变化,但您了解基本概念。)

      您通常可以通过使用 compressionQuality 小于 1.0 的 UIImageJPEGRepresentation 来缓解此问题(例如,0.8 或 0.9 提供最小的图像质量损失,但在 NSData 站点中可观察到减少)。但这是一种有损压缩。此外,您会在此过程中丢失一些图像元数据。

    2. 我相信您同时在内存中保存了同一图像的多个副本:您同时拥有 UIImage 表示和 NSData 对象。

    3. 不仅资产的NSData 表示比它需要的大,而且您还一次将整个资产加载到内存中。这不是必需的。

    相反,您可以考虑将原始资产从ALAssetLibrary 直接流式传输到持久内存,而不使用UIImagePNGRepresentationUIImageJPEGRepresentation,并且根本不将其加载到UIImage 中。相反,创建一个小缓冲区,通过getBytes 使用原始资产的部分重复填充此缓冲区,然后使用NSOutputStream 将此小缓冲区写入临时文件。您可以重复该过程,直到将整个资产写入持久存储。此过程的总内存占用远低于替代方法。

    例如:

    static NSInteger kBufferSize = 1024 * 10;
    
    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
    {
        NSURL *url = info[UIImagePickerControllerReferenceURL];
    
        [self.library assetForURL:url resultBlock:^(ALAsset *asset) {
            ALAssetRepresentation *representation = [asset defaultRepresentation];
            long long remaining = representation.size;
            NSString *filename  = representation.filename;
    
            NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
            NSString *path = [documentsPath stringByAppendingPathComponent:filename];
            NSString *tempPath = [self pathForTemporaryFileWithPrefix:@"ALAssetDownload"];
    
            NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:tempPath append:NO];
            NSAssert(outputStream, @"Unable to create output stream");
    
            [outputStream open];
            
            long long representationOffset = 0ll;
            NSError *error;
    
            uint8_t buffer[kBufferSize];
    
            while (remaining > 0ll) {
                NSInteger bytesRetrieved = [representation getBytes:buffer fromOffset:representationOffset length:sizeof(buffer) error:&error];
                if (bytesRetrieved < 0) {
                    NSLog(@"failed getBytes: %@", error);
                    [outputStream close];
                    [[NSFileManager defaultManager] removeItemAtPath:tempPath error:nil];
                    return;
                } else {
                    remaining -= bytesRetrieved;
                    representationOffset += bytesRetrieved;
                    [outputStream write:buffer maxLength:bytesRetrieved];
                }
            }
    
            [outputStream close];
            
            if (![[NSFileManager defaultManager] moveItemAtPath:tempPath toPath:path error:&error]) {
                NSLog(@"Unable to move file: %@", error);
            }
    
        } failureBlock:^(NSError *error) {
            NSLog(@"assetForURL error = %@", error);
        }];
    }
    
    - (NSString *)pathForTemporaryFileWithPrefix:(NSString *)prefix
    {
        NSString    *uuidString = [[NSUUID UUID] UUIDString];
    
        // If supporting iOS versions prior to 6.0, you can use:
        //
        // CFUUIDRef uuid = CFUUIDCreate(NULL);
        // assert(uuid != NULL);
        // NSString *uuidString = CFBridgingRelease(CFUUIDCreateString(NULL, uuid));
        // CFRelease(uuid);
        
        return [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@", prefix, uuidString]];
    }
    

    【讨论】:

    • Rob 是否可以从ALAssetsRepresentationNSData 格式获取缩略图,所以我可以完全避免使用UIImagePNGRepresentationUIImageJPEGRepresentation
    • @XaviValero 您可以只使用ALAssetthumbnail 方法来获取缩略图。或者您可以使用一些 image resizing routine 来调整默认表示。
    • 顺便说一句,使用新的照片框架,您现在可以使用requestImageDataForAsset 获取资产的原始NSData。见stackoverflow.com/a/27709329/1271826
    【解决方案2】:

    我通过发送 4 通道图像(RGBA 或 RGBX)而不是 3 通道图像 (RGB) 来解决此问题。 您可以检查是否有机会更改图像的参数。

    使用kCGImageAlphaNoneSkipLast 而不是kCGImageAlphaNone

    【讨论】:

      猜你喜欢
      • 2012-08-05
      • 1970-01-01
      • 2017-02-20
      • 1970-01-01
      • 1970-01-01
      • 2011-10-08
      • 2013-01-20
      • 2011-10-31
      • 2019-08-10
      相关资源
      最近更新 更多