Consider a sample SwiftData project
var body: some View {
List(recipes) { recipe in
NavigationLink(recipe.name, destination: RecipeView(recipe))
}
}
For SwiftUI views that uses SwiftData it's very straight forward.
However, if I need to read/write to SwiftData while in the background (let's say after a network call), the app crashes.
Imagine a simple workflow where
(1) user selects some model objects (via SwiftData modelContext)
(2) now I need to pass these objects to some API server
(3) in a background thread somewhere I need to use the modelActor but it now cannot read/write to the original model objects without risk of crashing
So instead, the only way I thought of is to create a separate modelContext for background processing. There I passed down the container from the main app, and creates a new ModelActor that takes in the container and owns a new modelContext of its own.
This works, without crash. However it introduces many side effects:
(1) read/write from this modelActor seems to trigger view changes for SwiftData's modelContext. But not vice versa.
(2) model objects fetched from one context cannot be used in another context.
(3) changes made in the actor also doesn’t not automatically sync with icloud but changes in SwiftData’s modelContext do.
So I guess the bigger question here is, what is the proper usage of SwiftData in a background thread?
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Post
Replies
Boosts
Views
Activity
In iOS 18 RC, and the iOS 18 simulator shipped with Xcode 16.0 RC, there is a regression where ModelContexts on the same ModelContainer do not sync changes. A minimal example is below, but briefly: create an object in context1. Retrieve and update that object in context2, then save context2. The changes cannot be found in context1 in iOS 18 RC, but can in iOS 17 and earlier betas of iOS 18.
I've submitted this as FB15092827 but am posting here for visibility to others. I'm going to have to scramble to see if I can mitigate this in our impacted app before iOS 18 launches. It's affecting us when doing background inserts in a ModelActor to populate our app UI, but you can see below the effects are seen even on the same thread in a very simple two-context example.
@Test("updates sync between contexts") func crossContextSync() async throws {
// overview:
// create an employee in context 1
// update the employee in context 2
// check that the update is available in context 1
let context1 = ModelContext(demoAppContainer)
let context2 = ModelContext(demoAppContainer)
// create an employee in context 1
let newEmployee = Employee(salary: 0)
context1.insert(newEmployee)
try context1.save()
#expect(newEmployee.salary == 0, "Created with salary 0")
// update the employee in context 2
let employeeID = newEmployee.uuid
let predicate: Predicate<Employee> = #Predicate<Employee> { employee in
employee.uuid == employeeID
}
let fetchedEmployee = try #require(try? context2.fetch(FetchDescriptor<Employee>(predicate: predicate)).first)
#expect(fetchedEmployee.uuid == newEmployee.uuid, "We got the correct employee in the new context")
let updatedSalary = 1
fetchedEmployee.salary = updatedSalary
try context2.save()
// FAILURE IS HERE. This passes in earlier iOS betas and in iOS 17.X
#expect(newEmployee.salary == updatedSalary, "Salary was update in context 1")
// Create a new modelContext on the same container, since the container does have the changes in it.
// By creating this new context we can get updated data and the test below passes in all iOS versions tested. This may be a mitigation path but creating new contexts any time you need to access data is painful.
let context3 = ModelContext(demoAppContainer)
let fetchedEmployeeIn3 = try #require(try? context3.fetch(FetchDescriptor<Employee>(predicate: predicate)).first)
#expect(fetchedEmployeeIn3.uuid == newEmployee.uuid, "We got the correct employee in the new context3")
#expect(fetchedEmployeeIn3.salary == updatedSalary, "Salary was update in context 1")
}
Code below if you want to build a working example, but the test above is very simple
let demoAppContainer = try! ModelContainer(for: Employee.self)
@main
struct ModelContextsNotSyncedToContainerApp: App {
init() {
}
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(demoAppContainer)
}
}
}
@Model
final class Employee {
var uuid: UUID = UUID()
var salary: Int
init(salary: Int = 0) {
self.salary = salary
}
}
I'm still getting this error (SwiftData/ModelContext.swift:3253: Fatal error: Failed to identify a store that can hold instances of SwiftData._KKMDBackingData<Presents_2024.Item> from [:]) in Xcode 16.1 Beta (16B5001e).
The app works for a limited amount of time and then crashes with this error. It looks like the SwiftData model isn't being created properly and when a context is saved it crashes.
Can you tell me if this error will be fixed in the next beta?
I'm still getting started with SwiftData and having trouble understanding how to persist a model with a relationship in a container. I've tried to distill the example below to be pretty simple.
Here are some example model definitions. I would like to be able to define a n Item. That Item can have SubItems related to it in a one-to-many fashion. Each SubItem is required to be attached to a parent Item.
final class Item {
var name: String
var subitems: [Item]?
init(
name: String,
subitems: [SubItem]? = nil
) {
self.name = name
}
}
@Model
final class SubItem {
var name: String
init(name: String) {
self.name = name
}
}
In my app I am then defining a preview container with some pre-saved data already.
Item(
name: "item1",
subitems: [
SubItem(name: "subItemA"),
SubItem(name: "subItemB")
]
),
Item(
name: "item2",
subitems: [
SubItem(name: "subItemC"),
SubItem(name: "subItemD")
]
)
]
@MainActor
let PreviewContainer: ModelContainer = {
do {
let schema = Schema([
Item.self,
SubItem.self,
])
let container = try ModelContainer(
for: schema, configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
for item in PreviewItems {
container.mainContext.insert(item)
for subItem in item.subitems! {
container.mainContext.insert(subItem)
}
}
return container
} catch {
fatalError("Failed to create container")
}
}()
My app is then defined as follows...
struct SwiftDataTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(PreviewContainer)
}
}
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
HStack {
VStack {
Text(items[0].name)
Text(String(items[0].subitems!.count))
// Text(items[0].subitems![0].name)
// Text(items[0].subitems![1].name)
}
Spacer()
VStack {
Text(items[1].name)
Text(String(items[1].subitems!.count))
// Text(items[0].subitems![0].name)
// Text(items[0].subitems![1].name)
}
}
.padding(100)
}
}
#Preview {
ContentView()
.modelContainer(PreviewContainer)
}
The preview loads without an issue, but if I uncomment the lines that access the SubItems it crashes. In the preview I can also see that each Item has 0 SubItems related to it. For some reason, the model container is not actually storing the `SubItem even though they are defined in PreviewItems.
Some things I've tried
Explicitly adding the relationship
Adding a Item property in SubItem to link back to it's parent
In the PreviewContainer definition, manually insert the SubItems as well as the parent Items
Any help is appreciated, thanks
Hi,
I understand how to make one to many relationship in SwiftData and how to show the child records, like all cities of a country. But how to navigate and show the parent record from a child record, Like I want to show a country of a city ?
like country.cities show all cities of a country, will cities.country work to show the country ? like cities.country.name ?
Kind Regards
Hello,
i have a route with many points
for routes:
@Model
public class Route(){
public id: UUID = UUID()
var name: String
var desc: String
var points: [Point] = []
}
@Model
public class Point(){
public id: UUID = UUID()
var speed: double
var route : Route
}
when I like to add point.
route.point.append(point)
I get all ways this error:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1cc1698ec)
my Xcode version 15.3
Hi,
I saw many examples on how to insert one model object at a time in SwiftData using modelContext.insert() , but can that command insert an array of objects in one batch as well ?
--
Kind Regards
Hi all,
I'm getting a strange SwiftData error at runtime in my voice recorder app. Whenever I attempt to generate and cache a samples array so that my app can visualize the waveform of the audio, the app crashes and the following pops up in Xcode:
{
@storageRestrictions(accesses: _$backingData, initializes: _samples)
init(initialValue) {
_$backingData.setValue(forKey: \.samples, to: initialValue)
_samples = _SwiftDataNoType()
}
get {
_$observationRegistrar.access(self, keyPath: \.samples)
return self.getValue(forKey: \.samples)
}
set {
_$observationRegistrar.withMutation(of: self, keyPath: \.samples) {
self.setValue(forKey: \.samples, to: newValue)
}
}
}
With an execution breakpoint on the line _$observationRegistrar.withMutation(of: self, keyPath: \.samples).
Here is my model class:
import Foundation
import SwiftData
@Model final class Recording {
var id : UUID?
var name : String?
var date : Date?
var samples : [Float]? = nil
init(name: String) {
self.id = UUID()
self.name = name
self.date = Date.now
}
}
And here is where the samples are being generated (sorry for the long code):
private func processSamples(from audioFile: AVAudioFile) async throws -> [Float] {
let sampleCount = 128
let frameCount = Int(audioFile.length)
let samplesPerSegment = frameCount / sampleCount
let buffer = try createAudioBuffer(for: audioFile, frameCapacity: AVAudioFrameCount(samplesPerSegment))
let channelCount = Int(buffer.format.channelCount)
let audioData = try readAudioData(from: audioFile, into: buffer, sampleCount: sampleCount, samplesPerSegment: samplesPerSegment, channelCount: channelCount)
let processedResults = try await processAudioSegments(audioData: audioData, sampleCount: sampleCount, samplesPerSegment: samplesPerSegment, channelCount: channelCount)
var samples = createSamplesArray(from: processedResults, sampleCount: sampleCount)
samples = applyNoiseFloor(to: samples, noiseFloor: 0.01)
samples = normalizeSamples(samples)
return samples
}
private func createAudioBuffer(for audioFile: AVAudioFile, frameCapacity: AVAudioFrameCount) throws -> AVAudioPCMBuffer {
guard let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: frameCapacity) else {
throw Errors.AudioProcessingError
}
return buffer
}
private func readAudioData(from audioFile: AVAudioFile, into buffer: AVAudioPCMBuffer, sampleCount: Int, samplesPerSegment: Int, channelCount: Int) throws -> [[Float]] {
var audioData = [[Float]](repeating: [Float](repeating: 0, count: samplesPerSegment * channelCount), count: sampleCount)
for segment in 0..<sampleCount {
let segmentStart = AVAudioFramePosition(segment * samplesPerSegment)
audioFile.framePosition = segmentStart
try audioFile.read(into: buffer)
if let channelData = buffer.floatChannelData {
let dataCount = samplesPerSegment * channelCount
audioData[segment] = Array(UnsafeBufferPointer(start: channelData[0], count: dataCount))
}
}
return audioData
}
private func processAudioSegments(audioData: [[Float]], sampleCount: Int, samplesPerSegment: Int, channelCount: Int) async throws -> [(Int, Float)] {
try await withThrowingTaskGroup(of: (Int, Float).self) { taskGroup in
for segment in 0..<sampleCount {
let segmentData = audioData[segment]
taskGroup.addTask {
var rms: Float = 0
vDSP_rmsqv(segmentData, 1, &rms, vDSP_Length(samplesPerSegment * channelCount))
return (segment, rms)
}
}
var results = [(Int, Float)]()
for try await result in taskGroup {
results.append(result)
}
return results
}
}
private func createSamplesArray(from processedResults: [(Int, Float)], sampleCount: Int) -> [Float] {
var samples = [Float](repeating: 0, count: sampleCount)
vDSP_vfill([0], &samples, 1, vDSP_Length(sampleCount))
for (segment, rms) in processedResults {
samples[segment] = rms
}
return samples
}
private func applyNoiseFloor(to samples: [Float], noiseFloor: Float) -> [Float] {
var result = samples
let noiseFloorArray = [Float](repeating: noiseFloor, count: samples.count)
vDSP_vsub(noiseFloorArray, 1, samples, 1, &result, 1, vDSP_Length(samples.count))
return result
}
private func normalizeSamples(_ samples: [Float]) -> [Float] {
var result = samples
var min: Float = 0
var max: Float = 0
vDSP_minv(samples, 1, &min, vDSP_Length(samples.count))
vDSP_maxv(samples, 1, &max, vDSP_Length(samples.count))
if max > min {
var a: Float = 1.0 / (max - min)
var b: Float = -min / (max - min)
vDSP_vsmsa(samples, 1, &a, &b, &result, 1, vDSP_Length(samples.count))
} else {
vDSP_vfill([0.5], &result, 1, vDSP_Length(samples.count))
}
return result
}
And this is how the processSamples function is used:
private func loadAudioSamples() async {
let url = recording.fileURL
if let audioFile = loadAudioFile(url: url) {
if recording.samples == nil {
recording.samples = try? await processSamples(from: audioFile)
}
}
}
private func loadAudioFile(url: URL) -> AVAudioFile? {
do {
let audioFile = try AVAudioFile(forReading: url)
return audioFile
} catch {
return nil
}
}
Any help or leads would be greatly appreciated! Thanks!
Hello,
i have an error with iCloud that says 'can't communicate with the server'.
Thanks!
I've had a persistent but hard to reliably reproduce issue when using SwiftData with SwiftUI.
Sometimes, when some properties of a SwiftData model object are used in the UI (like the title of a note in a list of notes) and something in the model is modified, the UI stops responding. The modification in the model could be a completely different object and of a different type and could be as simple as toggling a Bool, it could be performed on the main thread from inside a Button's action using the ModelContext from the environment or on a separate thread using a ModelActor. The issue even appears and disappears between builds of the same code.
As an example, this is the kind of code that leads to this issue, though I haven't been able to make a minimal reproduction because of how inconsistently it appears:
@ModelActor
struct NoteModifier {
func append() {
guard let notes = try? modelContext.fetch(FetchDescriptor<Note>()) else { return }
notes.randomElement()!.title.append("ABC")
try? modelContext.save()
}
}
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var notes: [Note]
var body: some View {
VStack {
List {
ForEach(notes) { note in
VStack {
Text(note.title)
Text(note.contents)
}
}
}
Button {
Task {
await NoteModifier(modelContainer: modelContext.container).append()
}
} label: {
Text("Bug")
}
}
}
}
When it happens and I try pausing execution, it's locked inside kevent_id in the getter for one of the properties of my model object like this:
In Instruments:
Most of my CloudKit data has been missing from my container for over a week. No response from Apple support. I am very worried it may be unrecoverable.my apps can not access the data and the dashboard shows a huge amount of files and data in other files missing. These are needed to operate our chiropractic clinic and contain patient and transaction history. This might be a disaster for us.
Hi all,
I have a book club app I'm developing that utilizes SwiftData and Cloudkit. From what I've learned, the way to display lists that can update in real time across multiple devices is to retrieve an array of the model objects via the Query keyword then use a computed property to sort and filter the array to your liking, and reference that computed property when you need to display the list. This works, but it seems inefficient to grab every single object of a given model and sort / filter it each time.
The other method I've been trying was, for example, after the user selects a Book object that comes from a queried list populated via the method above, to pass that Book object through the program so I can access its properties to display their lists (and it should work right because that initial book property came directly from a queried array of Books?).
For instance, a Book object has an array of Readers. After a user selects a Book, I can pass that Book object to the ReaderView class and display itsreaders array, and it should update in real time on CloudKit since that Book object came from a queried Books array. The benefit of this is you don't have to do as much sorting and filtering. But unfortunately, I've had mixxed results doing it this way, and it seems that the further you get away from the original object, the less success I've had getting it to update in real time. For instance, if I try to access a Reader objects Comments array property, it might not work.
Anyways, I'm wondering if I should generally just keep it simple and stick to the first method I mentioned, or if there is a pattern that people generally follow?
Hi all,
I have a SwiftUI-SwiftData-CloudKit app that has a TextField. I would like to make it so that after a user types in text, it will update the TextField on another device via CloudKit. I have the following code:
{
private let comment:Comment
@Query private var comments: [Comment]
@State private var text: String = ""
@State private var userInput = ""
private var filteredComment: Comment?
{
if let comment = self.comments.first(where: { $0 == self.comment })
{
return comment
}
else
{
return nil
}
}
init(comment: Comment)
{
self.comment = comment
}
var body: some View
{
VStack
{
if let filteredComment = self.filteredComment
{
TextField("", text: self.$text)
.onAppear()
{
self.text = filteredComment.text
}
.onChange(of: self.text)
{
filteredComment.text = text
}
}
}
}
}
This code actually works fine and will update to another device via CloudKit when a user exits out of the view and then reloads it. But I would like for it to update to the TextField after the user finishes typing or while they're typing. Weirdly, it will update on the fly in another screen I have in the app that displays the same text in a list. I've tried saving the modelContext after loading self.filteredComment and it makes no difference. Anyone know what's going on here? Thanks.
Is it ok to have latency about 4 sec? The amount of downloaded data is less than 1 MB. Maybe I need to setup an index for every field requested?
Hi All,
I used to be able to query all my records in dev for years. It seems today i have a bug where I can't query any of my records prior to 7/25/2024. I am able to query the older records using the record name but using the createdtimestamp or any other field i cannot access my records before 7/25/2024.
Anyone else having a similar issue?
I was on vacation last week and when I returned, I discovered that a sizable number of records in my iCloud database had been deleted! I am at a complete loss on this could have occurred. I have several questions:
Is there anyway to contact Apple iCloud support teams?
Does iCould have backups of data?
I have been using the basic NSPersistentContainer with 100k+ records for a while now with no issues. The database size can fluctuate a bit but on average it takes up about 22mb on device.
When I switch the container to NSPersistentCloudKitContainer, I see a massive increase in size to ~150mb initially. As the sync engine uploads records to iCloud it has ballooned to over 600mb on device. On top of that, the user's iCloud usage in settings reports that it takes up 1.7gb in the cloud.
I understand new tables are added and history tracking is enabled but the size increase seems a bit drastic. I'm not sure how we got from 22mb to 1.7gb with the exact same data.
A few other things that are important to note:
I import all the 100k+ records at once when testing the different containers. At the time of the initial import there is only 1 relation (an import group record) that all the records are attached to.
I save the background context only once after all the records and the import group have been made and added to the context.
After the initial import, some of these records may have a few new relations added to them over time. I suppose this could be causing some of the size increase, but its only about 20,000 records that are updated.
None of the records include files/ large binary data.
Most of the attributes are encrypted.
I'm syncing to the dev iCloud environment.
When I do make a change to a single attribute in a record, CloudKit reports that every attribute has been modified (not sure if this is normal or not )
Also, When syncing to a new device, the sync can take hours - days. I'm guessing it's having to sync both the new records and the changes, but it exponentially gets slower as more records are downloaded. The console will show syncing activity, but new records are being added at a slower rate as more records are added. After about 50k records, it grinds to a halt and while the console still shows sync activity, only about 100 records are added every hour.
All this to say i'm very confused where these issues are coming from. I'm sure its a combination of how i've setup my code and the vast record count, record history, etc.
If anyone has any ideas it would be much appreciated.
I am getting the following error:
Failed to save diary entry to CloudKit: <CKError 0x6000035adec0: "Server Rejected Request" (15/2000); op = 10F3CACEA9EC09B6; uuid = 22DDB0B8-9F1B-4BE6-A51B-2ADD08B469B1; container ID = "iCloud.com.domainname.productname">
What should I do to prevent this from happening?
I put the right container name and still this happens.
I was expecting the data model object to save in the iCloud.
In Swift Data I have a basic model
@Model class MovieSD {
@Attribute(.unique) var title: String
var genre: [String] = [String]()
init(title: String) {
self.title = title
}
}
I am trying to create predicates when fetching the data. Creating a predicate to search by title works as expected
let movieTitle = #Predicate<MovieSD> { movie in
movie.title.contains("filterstring")
}
But when attempting to do the same for the String array
let movieGenre = #Predicate<MovieSD> { movie in
movie.genre.contains("filterstring")
}
Results in a crash with EXC_BAD_ACCESS
Similar approaches produce a different error and point to the likely issue.
let movieGenre2 = #Predicate<MovieSD> { movie in
if movie.genre.contains(where: { $0 == "filterstring" }) {
return true
} else {
return false
}
}
Results in a crash with the error:
error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x281a96840> , Can't have a non-relationship collection element in a subquerySUBQUERY(genre, $$_local_1, $$_local_1 == "SciFi") with userInfo of (null)
or alternatively largely the same error for:
let movieGenre3 = #Predicate<MovieSD> { movie in
movie.genre.filter { genre in
return genre == "filterstring"
}.count > 0
}
But I couldn't seem to find an approach to create a SUBQUERY with #Predicate
Naturally, I can use similar to the above to filter the array that is returned. But this seems inefficient. And it seems it should be possible to filter
Any help showing how to filter a String array with a predicate with Swift Data would be appreciated
We're using Swift Data and syncing it to iCloud.
For the most part this is working.
Our problem is when we delete the App, and install the latest version from TestFlight all our Swift Data Meeting tables are gone. If we create a Meeting on this instance it works, and we can display multiple meetings, but all our prior meetings are gone.
Now if we just build the App in Xcode and overwrite the install, all the prior Swift Data meetings show up, in addition the above created meetings also show up.
If we don't delete the App, and just install the TestFlight build over the Xcode build it also works, all the meetings show up.
So it's as if there are 2 Containers, one for Development and one for Production (the TestFlight build), and they are not sync'd with each other.