ios swift - unreleased memory when appending uiimage to array - ios

I'm appending uiimages from core data to an array in order to create a gif of them.
I'm creating the gif and then emptying the array; however, I still have an indefinite 50 mb memory allocation from the moment I create the gif.
I tried looking at instruments and this is what I'm getting in the call tree:
Call tree image
This is the code:
do{
let objects = try managedObjectContext.executeFetchRequest(request)
let results = objects
if results.count > 0 {
for var i = 0; i < results.count; i += 1{
let match = results[i] as! cFW
date.append(match.date)
let image = match.image
fetchedImage.append(UIImage(data: image)!)
}
} else {
}
}
catch{}
Even after deleting the gif and the array already being deleted, the app stays at 50mb of memory usage.
Thank you
edit: The issue that I'm having has to do with how I'm displaying the gif (in a webview). I will update the question with my solution asap

The issue was happening due to presenting the gif in a webview.
Clearing the cache of web view did not resolve the allocation of memory.
I ended up using a library to FLAnimatedImage to present my GIF.
No more unreleased memory.
Thank you guys

Related

Does showing system image(SF symbols) use a networking call in Swift?

I'm creating an application in ios where I load images from an api using a UITableView and UITableViewCell.
Since the UITableView reuses cells, old images were appearing when I scroll fast. In order to prevent this, I set a default image using a system image(SF symbols).
I also use a cache to store urls to images.
Everything works as it should but now I think of it I'm sending a network request to retrieve that systemImage each time which seems incredibly inefficient since I was using a cache in order to reduce the total network calls in the first place.
Is there way around this or is this a tradeoff I must make?
Code is below.
//use default image from SF symbols
let defaulticon = UIImage(systemName: "photo")?.withTintColor(.gray, renderingMode: .alwaysOriginal)
DispatchQueue.main.async {
cell.mealImage.image = defaulticon
}
guard cell.meal?.strMealThumb != nil else {
print("Category Image doesn't exist")
return
}
//use cache
if let imageData = model.imagecache.object(forKey: cell.meal!.strMealThumb as NSString) {
print("using cache")
DispatchQueue.main.async {
cell.mealImage.image = imageData
}
}
else {
let url = URL(string: cell.meal!.strMealThumb)
let session = URLSession.shared.dataTask(with: url!) { data, response, error in
if error == nil && data != nil {
let image = UIImage(data: data!)
//self.model.imagecache[cell.meal!.strMealThumb] = image
self.model.imagecache.setObject(image!, forKey: cell.meal!.strMealThumb as NSString)
DispatchQueue.main.async {
cell.mealImage.image = image
}
}
}
session.resume()
}
}
Override prepareForReuse method in UITableViewCell and add code in this function to clean up unrequited data that could persist from previous usage of the cell. In your example assign the default image in this function to produce better result.
You asked:
I set a default image using a system image(SF symbols).
...
Everything works as it should but now I think of it I'm sending a network request to retrieve that systemImage each time which seems incredibly inefficient since I was using a cache in order to reduce the total network calls in the first place.
No, UIImage(systemName:) does not make a network request. And it caches the image, itself, as the documentation says:
This method checks the system caches for an image with the specified name and returns the variant of that image that is best suited for the main screen. If a matching image object is not already in the cache, this method creates the image from the specified system symbol image. The system may purge cached image data at any time to free up memory. Purging occurs only for images that are in the cache but are not currently being used.
FWIW, you can empirically verify that this does not perform a network request disconnecting from the network and trying to use it. You will see it works fine, even when disconnected.
FWIW, there is a very small performance gain (less than a millisecond?) by keeping a reference to that tinted system image and reusing it, rather than fetching the cached system image and re-tinting it. But the performance improvement is negligible.

Can memory in my code be better managed when using UIImageView.animationImages and startAnimating()?

I'm declaring some UIImage arrays:
var animationImages1: [UIImage] = []
var animationImages2: [UIImage] = []
I'm using a background thread to load the images:
DispatchQueue.global(qos: .background).async { () -> Void in
self.animationImages1 = self.createImageArray(total: 57, imagePrefix: "animation1")
self.animationImages2 = self.createImageArray(total: 42, imagePrefix: "animation2")
}
The function called above:
var imageArray: [UIImage] = []
for imageCount in 1..<total {
var imageName = String(format: "\(imagePrefix)\(imageCount)")
let image = UIImage(contentsOfFile: Bundle.main.path(forResource: imageName, ofType: "png")!)!
//let image = UIImage(named: imageName)!
imageArray.append(image)
}
return imageArray
Then when I want to animate, I'm calling this function:
func animate(imageView: UIImageView, images: [UIImage], duration: TimeInterval) {
imageView.animationImages = images
imageView.animationDuration = duration
imageView.animationRepeatCount = 1
imageView.startAnimating()
}
I tried doing both theImageView.stopAnimating() and theImageView.animationImages = nil before calling the animation again but didn't notice any improvement to memory management using either.
With UIImage(named:) the images visible in the app start disappearing either partially or completely as memory runs low. With UIImage(contentsOfFile:) the app promptly crashes once memory runs low.
To note: I tried UIImage(named:) with the images in the Assets catalog, and then switched to UIImage(contentsOfFile:) with the images dragged in to the project outside of Assets.xcassets
Is it possible to use this function of UIImageView for longer animations (2-5 seconds: 40-150 pngs) with a file size of about 450k each, or is it too much a memory strain regardless of how you go about it?
It currently runs without an issue on a newer iPad Pro, and using Xcode's simulator it runs well (but eats a lot of memory) on all device sizes. On an iPhone X and an iPhone 8 Plus, it runs out of memory pretty early on - after playing through 5 to 10 animations or so.
Am I missing something, is it not possible, or do I need to do further research on ways to keep memory in check while running these large UIImage arrays through startAnimating()?
Memory usage is not going down. I must be caching this somewhere...
Thanks for any help!
I created a new Xcode project simplified to focus just on this issue, and was given a correct answer by #Stephan Schlecht here.
Although the memory hit doesn't occur when assigning the images to an array, and the memory hit only happens once the animation is played, still the only way to reclaim the memory seems to be removing all references to the image array by setting both the variable array and the animationImages property on the UIImageView to a blank array.
So in the particular example given in this question:
animationImages1 = []
imageView.animationImages = []

Running out of memory in a for loop swift (4)

I'm looping through all pages in a PDFDocument (200+ pages) but app crashes with
Message from debugger: Terminated due to memory issue
The pdf is approx 4mb in size yet each iteration of the loop jumps the memory up approx 30mb. Which doesn't seem right to me. I have managed to locate where in my code the memory is being used just not sure how to claim it back. Tried setting variables to nil but no effect. Tried code in the for loop in an autoreleaspool{} but no effect.
#objc func scrapePDF(){
let documentURL = self.documentDisplayWebView!.url!
let document = PDFDocument(url: documentURL)
let numberOfPages = document!.pageCount
DispatchQueue.global().async {
for pageNumber in 1...numberOfPages {
print(document?.page(at: pageNumber)!.string!)
}
}
}
UPDATE: solved ..... kind of
Playing around a bit I found that rather than passing a reference to the PDFDocument inside the loop, if instead I create a new instance for each loop this strangely solves the memory issue. I don't quite understand why though. PDFDocument is a Class not a Struct so is passed by reference. Meaning it is only created once and then referenced to inside my loop. So why would it cause a memory issue?
#objc func scrapePDF(){
let documentURL = self.documentDisplayWebView!.url!
let document = PDFDocument(url: documentURL)
let numberOfPages = document!.pageCount
DispatchQueue.global().async {
for pageNumber in 1...numberOfPages {
let doc = PDFDocument(url: documentURL)
print(doc?.page(at: pageNumber)!.string!)
}
}
}
Though the above code clears the memory issue the problem with it is that its too slow. Each loop takes 0.5 seconds and with 300+ pages I can't accept that. Any tips on speeding it up? Or why it doesn't give the memory back if referencing the PDFDocument from outside the loop
Further UPDATE.
It seems that it’s calling the .string method of the PDFPage that is increases the memory to the point of crashing.

UIImageJPEGRepresentation using large amount of memory (Swift 3.0)

I'm trying to compress and get the NSdata from between 20 and 30 UIImages with a "for-loop" like this:
for theImage in selectedUIImages {
let data = UIImageJPEGRepresentation(image,0.5)
// doing something with the data
}
Tried on an iPhone 7 with no issues besides my app using upto 700MB of memory when going through the loop, but on an older iPhone I get the message:
*Message from debugger: Terminated due to memory issue.*
The main objective is to get the NSData from the UIImage so I can put the image in a dir for uploading. Let me explain:
The Amazon S3 Transfer utility wants a path/url to the image and therefore I need to make a path/url for the UIImage and the only way i know is to get it by:
data.write(to: URL(fileURLWithPath: localPath), options: .atomic)
Try using an autorelease pool:
autoreleasepool {
for theImage in selectedUIImages {
let data = UIImageJPEGRepresentation(image,0.5)
// doing something with the data
}
}
and move it in a background thread.
Because your app run out of memory.
You can save it to Document directory after compress then upload it to server one by one. So it not make your memory issue.
You can decrease the image size by decreasing a ratio parameter. You can use 0.3 instead 0.5.
for theImage in selectedUIImages {
let data = UIImageJPEGRepresentation(image,0.3)
// doing something with the data
}

Watch kit App : Terminated due to memory error

Hi I am developing an app in which I require to chache 50 images (size of all images is 2.5 mb) ,It is chaching the images but also increases the memory by 10 mb in Apple Watch App due to which app crashes .
Xcode gives error in xCode “Message from debugger: Terminated due to memory error”
The code i am using is below :
for (var i : Int = 1; i<26; i++) {
let filenameHuman = NSString(format: "human_%d", i )
let filenameZombie = NSString(format: "zombie_%d", i )
var imageHuman : UIImage! = UIImage(named: filenameHuman as String)
var imageZombie : UIImage! = UIImage(named: filenameZombie as String)
WKInterfaceDevice.currentDevice().addCachedImage(imageZombie, name: filenameZombie as String)
WKInterfaceDevice.currentDevice().addCachedImage(imageHuman, name: filenameHuman as String)
}
NSLog("Currently cached images: %#",WKInterfaceDevice.currentDevice().cachedImages)
Also the screenshot of Memory allocation and memory leak is :
Please Help, Thanks in advance .
Are any of your images actually animations (that would use up more space)?
Collect the return value of each call to addCachedImage(). False means it could not be added -- you need to check to that and it might give clues as to a particular problem image.
Before calling anything, try to empty the cache, removeAllCachedImages. This means you will be clean from previous cache interactions using up the pool of memory.
I think your problem is not a leak, I think your problem is over-retained allocations. So use the Allocations tool (with retain count tracking) to see how much memory was allocated (VM allocations) and how many entities are holding on to such memory (retain counts).
Inside your loop try to autorelease the memory that is used by the images since you don't want to wait for autorelease to happen later when the method returns.
for (var i : Int = 1; i<26; i++) {
autoreleasepool {
/* code to cache images */
}
}

Resources