I am loading images into a SwiftUI List. When too many images are scrolled down, RAM skyrockets and crashes app. Why are images not deallocated as user scrolls down past them?
I am loading images as so:
List(allProducts, id: \.self) { product in
Image(uiImage: UIImage(data: dataFromRealmDB[product]))
}
My intuition tells me that there must be something to deallocate it manually from memory, so I am trying the following. Please let me know if you how to fill in the blank.
List(allProducts, id: \.self) { product in
Image(uiImage: UIImage(data: dataFromRealmDB[product])).onDisappear(perform: {
"WHAT SHOULD GO HERE TO MAKE THE IMAGE GET PURGED FROM RAM?"
}
}
If my suggested solution is not possible, please let me know as well.
UPDATE
I have changed the way images are stored. Now they are stored with FileManager instead of saving them to the RealmDB. This is my function to get the image. Still memory usage increase causing a crash, there is no deallocation from SwiftUI.
func getImage(link: String) -> Image? {
let lastPath = URL(string: link)?.lastPathComponent
if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
let image : UIImage? = UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(lastPath!).path)
if image != nil {
return Image(uiImage: UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(lastPath!).path)!)
}
}
return Image("noImg")
}
CodePudding user response:
Are you sure it is not related to the fact you fetch the image raw data from the Database? According to List works like a tableview - i.e reusing cells.
I think the fact you are using a data base to hold the raw data of the images causes the spike in memory.
This is kind of an opinion based answer but I’d recommend on either bundling the images in advance in the application (if your business logic supports it), using names in the DB and init them by name.
Or hosting them remotely and fetching on demand.
CodePudding user response:
The code in the question is a bit incomplete but the answer really isn't a coding answer, it's more of a overall design answer.
Realm objects are lazily loaded and will essentially never overrun memory if used properly; they are only in memory when in use; e.g. a Realm Results object having 10,000 objects is easily handled, memory is allocated and deallocated automatically.
On that note, if you store Realm objects in other, non-Realm objects like an array, that totally changes the memory impact and can overwhelm the device.
But, and more importantly:
Realm is not a good way to store Blob data (full size pictures). A Realm property has a finite amount of storage of 16Mb and an image can easily go way beyond that.
There are other options for picture storage from MongoDB and Firebase.
See my answer to this question for more details
Lastly, you should be using pagination to control how many images are loaded at a time from whatever source you use; that will allow you to more easily control the memory allocation and UI.
So that last part is important, no matter what the technique is, loading a bunch of images into memory is going to eventually overwhelm the device so as you can see, switching to using FileManger to load the images from disk instead of Realm is not going to be a long term solution; pagination is the way to go.
There are some third party libraries available to help with that and/or you can craft your own. Do a search here on SO for 'image pagination' as it's discussed a lot
Oh - one other thing; please use thumbnails for your UI! You only really need to display full size images if the user taps or selects it - then you can load it from disk. Firebase Storage can actually do that for you; when you upload an full size image, it can (on their server) create a thumbnail for your UI.
Likewise, thumbnails are tiny and Realm can easily handle those; the design would be an object like this
class MyImageObject: Object {
@Persisted var imageURL: //a string or url to where it's stored on disk
@Persisted var thumbnail: Data!
@Persisted var image_name = "" //the image name
}