It is very important to track where and how the properties are accessed. In my case my core data stack is build with one parent NSManagedObjectContext and temporary child NSManagedObjectContext, which are doing the writing and deleting on a background thread, with the guidance of this article.
In my case the problem was with one (unusually) complex operation involving few nested for loops and async calls to the back-end. Because of the way our CMS system works we can not fetch the data normally, so we will have to resolve to the nested loops. So, imagine a lobby page with different tabs of games. Each tab has sections of games. In order to populate it I am:
- Fetching the tabs
- I save them in Core Data with a block, which underneath is creating a new instance of NSManagedObjectContext on its own background thread (check the linked article above)
- After they are saved I am iterating over them and with their id I am making an asynchronous request using Swift 5.5's Async/Await functions to fetch all game groups and games
- I save the results in Core Data
It is so obvious that it is easy to overlook, but after spending a day and a half in debugging I figured it out.
When I store the tab entities with the temporary NSManagedObject I am then creating a Task and I use the tab's identifier
property to fetch the games. Which unfortunately leads to crash because. But doing so I am introducing another thread in the picture and the app crashes.
Task.detached {
// `tab`'s MOC is created in a different thread and accessing it's properties can lead to a crash
let tabId = tab.tabId
}
Now, in order to fix this the safest way I managed to find is:
let tabId = tab.tabId
Task.detached {
...fetch the games using the `tabId` value
if let threadSafeTab = managedObjectContext.object(with: tab.objectID) as? GamesTab {
// Now the entity is synced properly and we can safely access it's properties
threadSafeTab.addToGamesSections(section)
}
}
For reference check Apple's documentation on object(with:)
I hope my answer is helpful :)