Thoms
Thoms

Reputation: 311

Memory leak when resizing UIImage

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?

Upvotes: 1

Views: 1749

Answers (3)

omanosoft
omanosoft

Reputation: 4339

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

Upvotes: 0

user7014451
user7014451

Reputation:

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

Upvotes: 2

Baig
Baig

Reputation: 4995

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

Upvotes: 0

Related Questions