当来自 UIImagePickerViewController 的选取器图像时,内存使用量极大?
Extreamly memory usage when picker image from UIImagePickerViewController?
我已经实现了上传图片的功能,但是在上传之前我会向用户预览图片。
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
但是突然我select预览图片,内存占用会超高,从30mb变成480mb。
这是在我将图像设置为 uiview 后发生的。内存将取决于图像大小,如果大尺寸我的内存也会变高。
进行小幅修改,以更正答案,如您要求 UIImagePickerController
的问题标题,但在问题文本中引用了 PHPickerViewController
的委托方法.这两个控制器均由 Apple 提供,处理从照片库中选取图像,PHPickerViewController
是 iOS 14+ 中可用的较新控制器,取代了旧的 UIImagePickerController
用于旧 iOS 版本。
提供的解决方案可以同时用于它们:
这是因为我假设您从 UIImage
中的选择器中获取结果并将其设置为 UIImageView
。 UIImage
s 可能会占用大量内存。例如,如果选择的图像是 12MP (3024 x 4032 = 12 192 768),当 OS 为它创建 UIImage
实例时,对于照片中的 12192768 个像素中的每个像素,使用 4 个字节内存 - 一个用于 alpha,一个用于红色,一个用于绿色,一个用于蓝色分量。
这意味着在phone内存中,将使用12192768 * 4 = 48 771 072字节(48 mb)。值得指出的是,此大小与图像在磁盘驱动器上时使用的大小不同,因为当图像存储在那里时,通常使用 JPG 或 PNG 等算法进行压缩。
我希望这能让您了解问题所在。
一种更有效的处理方法是从磁盘中获取作为 URL
的拾取图像,根本不实例化 UIImage
,然后从中加载缩小的表示形式到UIImage
,仅用于在其小 phone 屏幕上向用户展示,同时上传原始图像。在某些情况下,您甚至可能决定使用缩小的图像,因为它可能足以满足您的用例。
以下是您在 UIImagePickerControllerDelegate
的 picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
中执行此操作的方法:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let url = info[.imageURL] as? URL else { return } // taking the result as `url` instead of uiimage
if let image = url.asSmallImage {
self.loadImage(image) // do whatever with the small image
}
picker.dismiss(animated: true)
}
其中 URL
的 .asSmallImage
定义为:
// MARK: - Memory Optimized Image Loading
extension URL {
/// Used for limiting memory usage when opening new photos from user's library.
///
/// Photos could consume a lot of memory when loaded into `UIImage`s. A 2000 by 2000 photo
/// roughly will consume 2000 x 2000 x 4 bytes = 16MB. A 10 000 by 10 000 photo will consume
/// 10000 * 10000 * 4 = 400MB which is a lot, give that in your app
/// you could pick up more than one photo (consider picking 10-15 photos)
var asSmallImage: UIImage? {
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(self as CFURL, sourceOptions) else { return nil }
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: 2_000,
] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }
let data = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(data, kUTTypeJPEG, 1, nil) else { return nil }
// Don't compress PNGs, they're too pretty
let destinationProperties = [kCGImageDestinationLossyCompressionQuality: cgImage.isPNG ? 1.0 : 0.75] as CFDictionary
CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
CGImageDestinationFinalize(imageDestination)
let image = UIImage(data: data as Data)
return image
}
}
其中 cgImage.isPNG
定义为:
// MARK: - Helpers
extension CGImage {
/// Gives info whether or not this `CGImage` represents a png image
/// By observing its UT type.
var isPNG: Bool {
if #available(iOS 14.0, *) {
return (utType as String?) == UTType.png.identifier
} else {
return utType == kUTTypePNG
}
}
}
我已经实现了上传图片的功能,但是在上传之前我会向用户预览图片。
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
但是突然我select预览图片,内存占用会超高,从30mb变成480mb。 这是在我将图像设置为 uiview 后发生的。内存将取决于图像大小,如果大尺寸我的内存也会变高。
进行小幅修改,以更正答案,如您要求 UIImagePickerController
的问题标题,但在问题文本中引用了 PHPickerViewController
的委托方法.这两个控制器均由 Apple 提供,处理从照片库中选取图像,PHPickerViewController
是 iOS 14+ 中可用的较新控制器,取代了旧的 UIImagePickerController
用于旧 iOS 版本。
提供的解决方案可以同时用于它们:
这是因为我假设您从 UIImage
中的选择器中获取结果并将其设置为 UIImageView
。 UIImage
s 可能会占用大量内存。例如,如果选择的图像是 12MP (3024 x 4032 = 12 192 768),当 OS 为它创建 UIImage
实例时,对于照片中的 12192768 个像素中的每个像素,使用 4 个字节内存 - 一个用于 alpha,一个用于红色,一个用于绿色,一个用于蓝色分量。
这意味着在phone内存中,将使用12192768 * 4 = 48 771 072字节(48 mb)。值得指出的是,此大小与图像在磁盘驱动器上时使用的大小不同,因为当图像存储在那里时,通常使用 JPG 或 PNG 等算法进行压缩。
我希望这能让您了解问题所在。
一种更有效的处理方法是从磁盘中获取作为 URL
的拾取图像,根本不实例化 UIImage
,然后从中加载缩小的表示形式到UIImage
,仅用于在其小 phone 屏幕上向用户展示,同时上传原始图像。在某些情况下,您甚至可能决定使用缩小的图像,因为它可能足以满足您的用例。
以下是您在 UIImagePickerControllerDelegate
的 picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
中执行此操作的方法:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let url = info[.imageURL] as? URL else { return } // taking the result as `url` instead of uiimage
if let image = url.asSmallImage {
self.loadImage(image) // do whatever with the small image
}
picker.dismiss(animated: true)
}
其中 URL
的 .asSmallImage
定义为:
// MARK: - Memory Optimized Image Loading
extension URL {
/// Used for limiting memory usage when opening new photos from user's library.
///
/// Photos could consume a lot of memory when loaded into `UIImage`s. A 2000 by 2000 photo
/// roughly will consume 2000 x 2000 x 4 bytes = 16MB. A 10 000 by 10 000 photo will consume
/// 10000 * 10000 * 4 = 400MB which is a lot, give that in your app
/// you could pick up more than one photo (consider picking 10-15 photos)
var asSmallImage: UIImage? {
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(self as CFURL, sourceOptions) else { return nil }
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: 2_000,
] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }
let data = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(data, kUTTypeJPEG, 1, nil) else { return nil }
// Don't compress PNGs, they're too pretty
let destinationProperties = [kCGImageDestinationLossyCompressionQuality: cgImage.isPNG ? 1.0 : 0.75] as CFDictionary
CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
CGImageDestinationFinalize(imageDestination)
let image = UIImage(data: data as Data)
return image
}
}
其中 cgImage.isPNG
定义为:
// MARK: - Helpers
extension CGImage {
/// Gives info whether or not this `CGImage` represents a png image
/// By observing its UT type.
var isPNG: Bool {
if #available(iOS 14.0, *) {
return (utType as String?) == UTType.png.identifier
} else {
return utType == kUTTypePNG
}
}
}