内存泄漏-UIImagePNGRepresentation [英] Memory Leak - UIImagePNGRepresentation
问题描述
我正在尝试将图像从UIImagePicker
复制到文档目录.我正在使用@"UIImagePickerControllerOriginalImage"
键从UIImagePickerDelegate字典中获取原始图像.我正在使用UIImagePNGRepresentation
将图像写入文件.当我添加(重复该过程)具有高分辨率(图像大小约为20 mb)的图像时,我遇到了内存问题.
I am trying to copy an image from UIImagePicker
to documents directory. I am using @"UIImagePickerControllerOriginalImage"
key to get the original image from the dictionary of UIImagePickerDelegate. I am writing the image to file using UIImagePNGRepresentation
. When I am adding(repeating the process) images with high resolution(image size approx 20 mb) I am facing memory issues.
我分析并使用了Xcode的内存泄漏功能,并放大了以下导致泄漏的代码.
I profiled and used the memory leak feature of the Xcode and it zoomed in on the following piece of code, which is responsible for the leakage.
@autoreleasepool {
imagesData = UIImagePNGRepresentation(images);
[imagesData writeToFile:name atomically:NO];
imagesData = nil;
//[UIImageJPEGRepresentation(images, 1.0) writeToFile:name atomically:YES];
}
我在这里看到了许多有关UIImagePNGRepresentation
引起的内存泄漏的问题.但是我还没有找到解决我问题的合适方法.需要帮助.
I have seen many questions here, regarding memory leaks caused by UIImagePNGRepresentation
. But I haven't found a proper solution to my problem. Need help.
推荐答案
我不知道UIImagePNGRepresentation
有任何泄漏",但这肯定是对内存的过度使用,但是这里有两个问题:
I'm unaware of any "leak" with UIImagePNGRepresentation
, but it certainly is an extravagant use of memory, but there are a couple of issues here:
-
首先,通过
UIImage
对原始资产进行往返处理,然后使用UIImagePNGRepresentation()
的过程效率很低,最终可能会产生比原始资产大得多的NSData
.例如,我选择了一张原始资产为1.5mb,UIImageJPEGRepresentation
(compressionQuality
为1.0)为6mb,UIImagePNGRepresentation()
约为10mb的照片. (这些数字在图像之间可能会发生很大变化,但是您有了基本的想法.)
First, the process of round-tripping the original asset through a
UIImage
and then usingUIImagePNGRepresentation()
is fairly inefficient and can end up with aNSData
that is considerably larger than the original asset. For example, I picked a photo whose original asset 1.5mb, theUIImageJPEGRepresentation
(withcompressionQuality
was 1.0) was 6mb, and theUIImagePNGRepresentation()
was about 10mb. (These numbers can change quite a bit from image to image, but you get the basic idea.)
您通常可以通过使用compressionQuality
小于<1.0的UIImageJPEGRepresentation
来缓解此问题(例如0.8或0.9可以提供最小的图像质量损失,但可以观察到的NSData
位置减少).但这是有损压缩.此外,在此过程中,您会丢失一些图像元数据.
You can often mitigate this problem by using UIImageJPEGRepresentation
with a compressionQuality
of less than 1.0 (e.g. 0.8 or 0.9 offers minimal image quality loss, but yet an observable reduction in NSData
site). But this is a lossy compression. Furthermore, you lose some image meta data in this process.
我相信您正在同一时间将同一图像的多个副本保存在内存中:您同时具有UIImage
表示形式和NSData
对象.
I believe you are holding multiple copies of the same image in memory at the same time: You have both the UIImage
representation as well as the NSData
object.
不仅资产的NSData
表示形式大于其所需的大小,而且还一次将整个资产加载到内存中.没必要.
Not only is the NSData
representation of the asset larger than it needs to be, you're also loading the entire asset into memory at one time. This is not necessary.
您可能会考虑将原始资产从ALAssetLibrary
直接流式传输到持久性内存,而不使用UIImagePNGRepresentation
或UIImageJPEGRepresentation
,也完全不将其加载到UIImage
中.而是创建一个小缓冲区,通过getBytes
重复用部分原始资产填充此缓冲区,并在使用过程中使用NSOutputStream
将此小缓冲区写入临时文件.您可以重复该过程,直到将整个资产写入持久性存储中.此过程的总内存占用量比替代方法要低得多.
You might consider, instead, streaming the original asset from the ALAssetLibrary
directly to persistent memory, without using UIImagePNGRepresentation
or UIImageJPEGRepresentation
, and without loading it into a UIImage
at all. Instead, create a small buffer, repeatedly filling this buffer with portions of the original asset via getBytes
, writing this small buffer to a temporary file using NSOutputStream
as you go along. You can repeat that process until the entire asset is written to persistent storage. The total memory footprint of this process is much lower than the alternative approaches.
例如:
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]];
}
这篇关于内存泄漏-UIImagePNGRepresentation的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!