Hi,
I've run into two problems using SwiftData in iOS 18 that are total show-stoppers for my app, causing it to run out of memory and be killed by the system. The same app runs fine on iOS 17.
The two problems are inter-related in a way I can't exactly diagnose, but are easy to reproduce.
Problem #1: Calling .count on the array that represents a relationship or Query causes the whole array of objects to be loaded into memory.
Problem #2: When a @Model object is loaded, properties that are declared with .externalStorage are loaded unnecessarily, and use tons of memory.
It's possible that #1 is normal behavior, exacerbated by #2.
I've written a test app that demonstrates the extreme difference in memory usage between the OS Versions. It uses a typical navigation pattern, with content counts on the left-side view. Each item has one thumbnail and one large image in .externalStorge. GitHub Source
When populated with 80 items, each containing one thumbnail and one large image in .externalStorge, the app launches in 17.5 using 29mb of memory. On iOS 18, in the same conditions, 592 mb.
When the first folder is selected, causing a list of thumbnails to load, iOS 17 uses just 86mb. iOS 18 uses 599mb, implying that all image data has already been loaded.
So I'm asking for help from Apple or the Community in finding a workaround. I've been advised that finding a workaround may be necessary, as this may not be addressed in 18.0.
Thanks in advance for any insight.
Radars: FB14323833, FB14323934
(See attached images, or try it yourself)
(You may notice in the 18.0 screenshots that the item counts don't add up right. That's yet another 18.0-SwiftData anomaly regarding relationships that I haven't tackled yet, but is also demonstrated by the sample app.)
I am unaware of how the team would address the issue. Also, we can't comment the future plan, as you might have known.
I think the major problem here is that the app loads the full image data when it doesn't need to, which consumes a lot of memory, and making the image data attribute a relationship is the way to avoid that. This has been a best practice in Core Data, as shown in the model of the Sharing Core Data objects between iCloud users sample. (Note that the sample goes even further to model thumbnail
as a relationship.)
The default data store in SwiftData is based on Core Data, and so I'd suggest that folks follow the best practice. I understand that changing the model is pain though :-(.
Using fetchCount(_:)
is another best practice. Yet if the full image data isn't loaded when you load an item, and your data set isn't quite large, counting the query result may not be an obvious issue for you.
The result returned by fetchCount(_:)
is not updated when the context is changed, unless you do another fetch. It will be up to you to review your code if that impacts your SwiftUI views.
Best,
——
Ziqiao Chen
Worldwide Developer Relations.