Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Flush Cache to Free up Memory when using UIImage - Swift 3.0

Tags:

ios

swift

uiimage

After reading a number of answers on SO and other articles (see below) what is the best method to manage memory when you are loading multiple images into an animation array?

http://www.alexcurylo.com/2009/01/13/imagenamed-is-evil/

Here are my goals:

  1. Create Animation Array (see code below)
  2. After the Animation plays flush the cache and load another animation (rinse, wash, repeat, you get it :)

As Apple states https://forums.developer.apple.com/thread/17888

Using the UIImage(named: imageName) caches the images, but in my case after playing 2-3 animations in a row iOS terminates the App (the OS does not respond to a low memory warning and instead terminates the App - see end of code below)

I don't want to cache the images and rather we could either:

  1. Flush the memory each time an animation completes and then load a new animation; or
  2. Flush the memory when the user moves to a new Scene

Here is my code:

// create the Animation Array for each animation

func createImageArray(total: Int, imagePrefix: String) -> [UIImage]{
    var imageArray: [UIImage] = []
    for imageCount in 0..<total {
        let imageName = "\(imagePrefix)-\(imageCount).png"
        let image = UIImage(named: imageName)! // here is where we need to address memory and not cache the images

        //let image = UIImage(contentsOfFile: imageName)! // maybe this?

        imageArray.append(image)
    }
    return imageArray
}


// here we set the animate function values

func animate(imageView: UIImageView, images: [UIImage]){
    imageView.animationImages = images
    imageView.animationDuration = 1.5
    imageView.animationRepeatCount = 1
    imageView.startAnimating()
}

// Here we call the animate function

animation1 = createImageArray(total: 28, imagePrefix: "ImageSet1")
animation2 = createImageArray(total: 53, imagePrefix: "ImageSet2")
animation3 = createImageArray(total: 25, imagePrefix: "ImageSet3")

func showAnimation() {
    UIView.animate(withDuration: 1, animations: {
        animate(imageView: self.animationView, images: self.animation1)

        }, completion: { (true) in

        //self.animationView.image = nil // Maybe we try the following?
        //self.animationView.removeFromSuperview()
        //self.animationView = nil

        })
    }

Based on SO responses, it looks like this may be the best method to prevent the images from being cached, but it doesn't seem to work in my code:

let image = UIImage(contentsOfFile: imageName)!

I have also tried this but it doesn't seem to work either:

func applicationDidReceiveMemoryWarning(application: UIApplication) {
    NSURLCache.sharedURLCache().removeAllCachedResponses()
}

I also tried the following article (removeFromSuperview) in the completion block but I couldn't get this to work either (see my code above):

https://www.hackingwithswift.com/example-code/uikit/how-to-animate-views-using-animatewithduration

New Code:

// create the Animation Array for each animation

func createImageArray(total: Int, imagePrefix: String) -> [UIImage]{
    var imageArray: [UIImage] = []
    for imageCount in 0..<total {
        let imageName = "\(imagePrefix)-\(imageCount).png"
        //let image = UIImage(named: imageName)! // replaced with your code below

    if let imagePath = Bundle.mainBundle.path(forResource: "ImageSet1" ofType: "png"),
    let image = UIImage(contentsOfFile: imagePath) {
         //Your image has been loaded   }

        imageArray.append(image)
    }
    return imageArray }
like image 486
Server Programmer Avatar asked Dec 10 '17 01:12

Server Programmer


1 Answers

It's pretty simple. UIImage(named:) caches images, and UIImage(contentsOfFile:) does not.

If you don't want your images to be cached, use UIImage(contentsOfFile:) instead. If you can't get that to work then post your code and we'll help you debug it.

Be aware that UIImage(contentsOfFile:) does not look for files in your app bundle. It expects a full path to the image file. You will need to use Bundle methods to find the path to the file and then pass that path to UIImage(contentsOfFile:):

if let imagePath = Bundle.mainBundle.path(forResource: "ImageSet1" ofType: "png"),
  let image = UIImage(contentsOfFile: imagePath) {
     //Your image has been loaded 
}

Your code is loading all the images for all 3 animations into an array of images and never releasing them, so the fact that the system caches those images seems pretty irrelevant. In a low memory condition the system should flush the cached copies of the images, but your code will still hold all those images in your arrays so the memory won't get freed. It looks to me like it's your code that's causing the memory problem, not the system's images caching.

Your code might look like this:

func createImageArray(total: Int, imagePrefix: String) -> [UIImage]{
    var imageArray: [UIImage] = []
    for imageCount in 0..<total {
        let imageName = "\(imagePrefix)-\(imageCount)"
        if let imagePath = Bundle.main.path(forResource: imageName,
            ofType: "png"),
          let image = UIImage(contentsOfFile: imagePath) {
              imageArray.append(image)
        }
    }
    return imageArray 
}
like image 174
Duncan C Avatar answered Sep 29 '22 07:09

Duncan C