内存泄漏 - UIImagePNGRepresentation

时间:2014-08-15 06:28:26

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

我正在尝试将图像从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 个答案:

答案 0 :(得分:5)

我没有意识到任何"泄漏"使用UIImagePNGRepresentation,但它肯定是对内存的过度使用,但这里有几个问题:

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

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

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

    < / LI>
  3. 资产的NSData表示不仅大于其需要,您还要将整个资产一次加载到内存中。这不是必需的。

  4. 您可以考虑将原始资产从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]];
    }
    

答案 1 :(得分:0)

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

使用kCGImageAlphaNoneSkipLast代替kCGImageAlphaNone