我正在尝试将图像从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
引起的内存泄漏的问题。但我没有找到解决问题的正确方法。需要帮助。
答案 0 :(得分:5)
我没有意识到任何"泄漏"使用UIImagePNGRepresentation
,但它肯定是对内存的过度使用,但这里有几个问题:
首先,通过UIImage
然后使用UIImagePNGRepresentation()
往返原始资产的过程效率相当低,最终会导致NSData
大得多比原始资产。例如,我选择了一张原始资产为1.5mb,UIImageJPEGRepresentation
(compressionQuality
为1.0)为6mb,UIImagePNGRepresentation()
为10mb左右的照片。 (这些数字可能会在图像之间发生相当大的变化,但您会得到基本的想法。)
您通常可以使用UIImageJPEGRepresentation
小于1.0的compressionQuality
来缓解此问题(例如,0.8或0.9可提供最低的图像质量损失,但NSData
网站可观察到的减少)。但这是一种有损压缩。此外,您在此过程中丢失了一些图像元数据。
我相信你同时在内存中保存了同一图像的多个副本:你同时拥有UIImage
表示和NSData
对象。
资产的NSData
表示不仅大于其需要,您还要将整个资产一次加载到内存中。这不是必需的。
您可以考虑将原始资产从ALAssetLibrary
直接流式传输到永久性内存,而不使用UIImagePNGRepresentation
或UIImageJPEGRepresentation
,而无需将其加载到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
。