iosimageuiimageuipickerviewcontroller

Extreamly memory usage when picker image from UIImagePickerViewController?


I have implement function for upload image but before upload I will preview image to user.

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) 

But suddenly I select image to preview, memory usage will super high change from 30mb to 480mb. It was happened after I set image to uiview. memory will depend on image size if big size my memory will get high too.


Solution

  • Making a small edit, to correct the answer, as in the question title you are asking for UIImagePickerController but in the question text a delegate method from PHPickerViewController is referenced. Both of these controllers are provided by Apple and are dealing with image picking from the photo library and PHPickerViewController is the newer one available from iOS 14+, replacing the old UIImagePickerController that is to be used on older iOS versions.

    The provided solution could be used on both of them:

    This is because i assume you take the result from the picker in UIImage and set it to the UIImageView. UIImages could have a really big memory usage. For example if the picked image is 12MP (3024 x 4032 = 12 192 768), as the OS creates UIImage instance for it, for each of the 12192768 pixels from the photo, 4 bytes are used in memory - one for alpha, one for red, one for green and one for the blue component.

    This means that in the phone memory, 12192768 * 4 = 48 771 072 bytes (48 mb) will be used. Its worth pointing out that this size is not the same as the one that the image is using when its on your disk drive, as when it is stored there its usually compressed with some algorithm like JPG or PNG.

    I hope this gives you an insight on what the issue is.

    One more efficient way to handle this is to get the picked image as URL from the disk without instantiating UIImage at all, and then from it, load a shrunken down representation from it into a UIImage, just for presentation to the user on its small phone screen, while uploading the original image. In some cases you might even decide to use the shrunken down image as it might be enough for your use case.

    Here's how you might do that in your picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) from UIImagePickerControllerDelegate:

        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)
        }
    

    where URL's .asSmallImage is defined as:

    // 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
        }
    }
    

    Where cgImage.isPNG is defined as:

    // 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
            }
        }
    }