How to Work with SwiftData in the Background in Swift 6
Especially when working with LLM APIs, there are many use-cases where we want to save the returned data from the API into existing SwiftData models. Here is how you do this in Swift 6!
I may be late to the party, but I’m only now starting to work with Swift 6 (after being super super scared to even try!), and it’s actually pretty cool once you get the hang of it. More importantly, it’s making me think through and architect my apps differently - in a good way.
In my latest app, I have a SwiftData model where some of the variables are processed by an LLM API in the background and the SwiftData model has to be updated with the results from the API. I’ll give the example of a Journal app with a simple model:
The app will use an LLM API to analyze the journal entry’s mood. So the mood will start off by being nil and then being filled in once we get the LLM API response. The mood variable is not the most critical part of this app, so it can be done fully on the background while the user can browse or add other journal entries.
So let’s try to attempt to write a nonisolated
class (to be executed in the background) to extract the mood from a journal entry:
It’s not a full on error, but it’s a warning, and my goal is to remove all the warnings to make sure my code is Swift 6 level SAFE!! So how do we fix it?
The Model Actor
To make sure that our SwiftData models are accessed, updated, and saved in a thread-safe manner on the background thread, we can use a ModelActor
. By default, the model actor will take in the model container and make the changes as needed.
The model actor will be the only one that deals with the actual models, so in order to pass around the model information to our Mood Extractor API, for example, we need to create a proxy object for the JournalEntry
:
Now whenever the MoodExtractor needs to access the JournalEntry
, it can send in the JournalEntry’s PersistentIdentifier
and get back the proxy JournalEntryInfo
object with the required information for making the API call in a safe way. We can write the getJournalEntryInfo
as follows:
Finally, when the API returns back the mood of the journal entry, we can add a way to update the identified JournalEntry
with the mood:
That’s it! Just with a few lines of code, the JournalEntryActor
will safely retrieve, update, and save SwiftData models in a thread-safe way IN THE BACKGROUND!
The Mood Extractor
The MoodExtractor
will be triggered to run in the background to extract the mood of the journal entry when the user clicks on the Save button after finishing editing their journal entry. So we can get the model container at the initialization (coming from SwiftUI), and use it to initialized the JournalEntryActor
:
Now we can easily use the journalEntryActor
to update our extractMood
function to be completely thread-safe!
Using the MoodExtractor in SwiftUI
When the user clicks on “Save” after editing the entry, the extractMood
function can be called in a detached (background task):
To make sure the result is updated in real-time on the UI, make sure to use the SwiftData Query macro, in our case to list out the journal entries:
The journal entries will automatically update with the mood as soon as they are saved in the background by the JournalEntryActor
!
That’s it! It’s a bit of overhead in the architecture to go between the Model / ModelActor / Background API / SwiftUI, but this makes sure your code is SAFE and reactive! Oh - and all of this can be done in Swift 5 as well (you can architect your code in this same way without the Swift 6 warnings to force you!).
Have thoughts? Let me know in the comments!
Nice Swift 6 migration! Maybe it will be more convenient to pass the JournalEntry.entry directly and just update the mood?