3

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")   
    }
MoLoW
  • 185
  • 9
  • You're probably using an array somewhere which is what's causing the memory issue but see my answer. – Jay Feb 18 '22 at 18:00

2 Answers2

3

Are you sure it is not related to the fact you fetch the image raw data from the Database? According this question a SwiftUI 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.

Jay
  • 34,438
  • 18
  • 52
  • 81
CloudBalancing
  • 1,461
  • 2
  • 11
  • 22
  • You might be right, but I need the images to be available offline. Where would you store the images so this doesn't happen? By bundling the images, where would they go? Swift's FileManager? Why wouldn't swift allow me to deallocate an object I created – MoLoW Feb 18 '22 at 02:20
  • Yes. You can save them to disk. Fetch the data on demand (using the FileManager). If you know all the images in advance you can add them to the asset catalog and access by name – CloudBalancing Feb 18 '22 at 06:08
  • Ok, I have actually spent the last 4 hours recoding my app so I can test this solution. I must say, it has been in the back of my mind for a while, but I never got to it. I really hope it works. I am currently downloading the 6000 images so I can run the test properly. – MoLoW Feb 18 '22 at 06:33
  • A Realm List object definitely does NOT work like a tableView and is unrelated so don't count on that. A Realm List is a lazily loaded collection of objects, a tableView is a UI element that contains no data. While cells may be deallocated and re-used, a List doesn't do that. The spike in memory is not directly caused by storing images in a database - it's how they are loaded - the same issue would occur if they are bundled the images with the app, if they are not correctly loaded. Hosting them remotely is on point and a great suggestion. – Jay Feb 19 '22 at 15:33
  • @Jay - no one claimed Realm List works as TableView. If you’d look at the link you can see I reffered to the List SwiftUI component. After reading through your thread no doubt that the problem is loading the data from the DB into some other component and lack if pagination – CloudBalancing Feb 20 '22 at 03:51
  • 1
    Wonderful! Just needed a bit more clarity in the question for someone who didh't see the small link (like me) to differentiate a SwiftUI List and a [Realm List Object](https://docs.mongodb.com/realm-sdks/swift/latest/Classes/List.html). All good to go! – Jay Feb 20 '22 at 14:04
2

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
}
Jay
  • 34,438
  • 18
  • 52
  • 81