Memory leak when resizing UIImage
Asked Answered
B

3

1

I've read through multiple threads concerning the topic but my problem still persists. When I'm resizing an Image with following code:

extension UIImage {
  func thumbnailWithMaxSize(image:UIImage, maxSize: CGFloat) -> UIImage {
    let width = image.size.width
    let height = image.size.height
    var sizeX: CGFloat = 0
    var sizeY: CGFloat = 0
    if width > height {
        sizeX = maxSize
        sizeY = maxSize * height/width
    }
    else {
        sizeY = maxSize
        sizeX = maxSize * width/height
    }

    UIGraphicsBeginImageContext(CGSize(width: sizeX, height: sizeY))
    let rect = CGRect(x: 0.0, y: 0.0, width: sizeX, height: sizeY)
    UIGraphicsBeginImageContext(rect.size)
    draw(in: rect)
    let thumbnail = UIGraphicsGetImageFromCurrentImageContext()!;

    UIGraphicsEndImageContext()

    return thumbnail

}


override func viewDidLoad() {
    super.viewDidLoad()

    let lionImage = UIImage(named: "lion.jpg")!

    var thumb = UIImage()

    autoreleasepool {
        thumb = lionImage.thumbnailWithMaxSize(image: lionImage, maxSize: 2000)
    }
    myImageView.image = thumb
}

...the memory is not released. So when I navigate through multiple ViewControllers (e.g. with a PageViewController) I end up getting memory warnings and the app eventually crashes. I also tried to load the image via UIImage(contentsOfFile: path) without success. Any suggestions?

Butte answered 6/9, 2017 at 10:34 Comment(0)
R
2

I noticed your code beginning two contexts but only ending one.

Here's my extension, which is basically the same as your's. Since I'm not having memory issues, it looks like that may be the issue.

extension UIImage {
    public func resizeToRect(_ size : CGSize) -> UIImage {
        UIGraphicsBeginImageContext(size)
        self.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext();
        return resizedImage!
    }
}
Rafaelita answered 6/9, 2017 at 12:14 Comment(2)
That was it! Thank you.Butte
thank you. I was using Share Extension and worked well for mePeristalsis
T
0

The problem is this:

UIGraphicsGetImageFromCurrentImageContext() returns an autoreleased UIImage. The autorelease pool holds on to this image until your code returns control to the runloop, which you do not do for a long time. To solve this problem, make thumb = nil after using it.

var thumb = UIImage()

autoreleasepool {
   thumb = lionImage.thumbnailWithMaxSize(image: lionImage, maxSize: 2000)
   let myImage:UIImage = UIImage(UIImagePNGRepresentation(thumb));
   thumb = nil
}
myImageView.image = myImage
Trinette answered 6/9, 2017 at 10:52 Comment(1)
unfortunately setting thumb = nil does not solve the problem. the memory is still growing bigger. only when I use the original image the memory is released as expected.Butte
C
0

I encountered a similar problem and after trying various solutions, I came across the following insights:

When dealing with images using UIImage and CGImage system methods, it consumes a significant amount of memory. For instance, a simple image with dimensions around 4000px x 3000px can take up approximately 45MB of memory. If your app needs to handle multiple images or if you're working with a share extension, it's possible for your app to exhaust all available memory and crash. Share extensions have a limited memory allocation of only 120MB, so sharing larger images (above 7000px in height/width) can easily lead to app crashes. This issue has even affected some of the most popular apps on iOS.

So, there is no straightforward solution other than avoiding the handling of large image files or using third-party tools.

One such open-source library that addresses this problem, which I used to solve this problem is SDWebImage, available at https://github.com/SDWebImage/SDWebImage. After importing this library into your project, you can use it to handle images without worrying about memory leaks or crashes.

Here's an example of how you can use SDWebImage in your project to resize or compress image:

func resizeImage(atPath imagePath: URL) -> UIImage? {
    guard let image = UIImage(contentsOfFile: imagePath.path) else { return nil }
    
    guard let thumbnailData = SDImageIOCoder.shared.encodedData(with: image, format: .undefined, options: [.encodeMaxPixelSize: CGSize(width: 1920, height: 1920), .encodeCompressionQuality: 0.7]) else { return nil }
    
    return SDImageIOCoder.shared.decodedImage(with: thumbnailData)
}
Catchy answered 7/6, 2023 at 15:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.