内存泄漏 - UIImagePNGRepresentation

3
我正在尝试将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引起的内存泄漏的问题。但是我没有找到解决我的问题的合适解决方案。需要帮助。

2个回答

5

我不知道UIImagePNGRepresentation存在任何“泄漏”的问题,但它确实是一种浪费内存的方式,这里有几个问题:

  1. 首先,通过UIImage来回转换原始资源,然后使用UIImagePNGRepresentation()是相当低效的,可能会得到一个比原始资源大得多的NSData。例如,我选择了一张1.5mb的照片,UIImageJPEGRepresentation(使用compressionQuality为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,有没有可能从ALAssetsRepresentation获取缩略图并以NSData格式返回?这样我就可以完全避免使用UIImagePNGRepresentationUIImageJPEGRepresentation了。 - Xavi Valero
@XaviValero,你可以直接使用ALAssetthumbnail方法来获取缩略图。或者你可以使用一些图片调整大小例程来调整默认表示的大小。 - Rob
顺便提一下,使用新的Photos框架,您现在可以使用requestImageDataForAsset来获取资产的原始NSData。请参见https://dev59.com/FYbca4cB1Zd3GeqPWXs6#27709329。 - Rob

0
我通过发送一个4通道图像(RGBA或RGBX)来解决这个问题,而不是发送一个3通道图像(RGB)。 你可以检查一下是否有改变图像参数的机会。
使用kCGImageAlphaNoneSkipLast代替kCGImageAlphaNone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接