Using SwiftData: Model not saved when inserting from background actor

Hi everyone,

I'm trying to make use of a background actor in my SwiftUI project. Inserting data works with the ModelContainer's mainContext. Another context in a ModelActor, however, fails to write into the same database.

I verify the results by opening the SQLite file on the file system. While the mainContext.insert call does indeed insert a row into the table, the ModelActor's context fails to do so. There is no error or message received in the ModelActor. The property autosaveEnabled is set to true.

I wrote a sample project to reproduce the issue in isolation. It consists of a single source file that introduces the Model ToDo, the ToDoView, initializes the ModelContainer and ModelActor. Please find the source code below.

Is there any mistake in my approach?


import SwiftUI
import SwiftData

@Model
final class ToDo {
  let title: String
  
  init(title: String) {
    self.title = title
  }
}

@main
struct testSwiftDataApp: App {
  
  @State
  var modelContainer: ModelContainer
  
  @State
  var backgroundData: BackgroundDataActor
  
  init() {
    let modelContainer: ModelContainer =
      try! ModelContainer(for: ToDo.self)
    
    self.modelContainer = modelContainer
    self.backgroundData = BackgroundDataActor(
      modelContainer: modelContainer
    )
  }
  
  var body: some Scene {
    WindowGroup {
      ToDoView(backgroundData: self.backgroundData)
        .modelContainer(modelContainer)
    }
  }
}

struct ToDoView: View {
  
  @Environment(\.modelContext)
  var modelContext
  
  @Query
  var todos: [ToDo]
  
  let backgroundData: BackgroundDataActor
  
  var modelContainer: ModelContainer {
    self.modelContext.container
  }
  
  var body: some View {
    VStack {
      Text("Add ToDo")
      TextField(
        "",
        text: .constant(
          "URL to database: " +
          "\(self.modelContainer.configurations.first!.url)"
        )
      )
      // This action will be invoked on the ModelActor's context
      Button {
        Task {
          let todo = ToDo(title: "Step 1")
          await self.backgroundData.store(
            id: todo.persistentModelID
          )
        }
      } label: {
        Text("Create ToDo in background")
      }
      
      // This action will be invoked on the mainContext
      Button {
        Task {
          let todo = ToDo(title: "Step 2")
          self.modelContainer.mainContext.insert(todo)
        }
      } label: {
        Text("Create ToDo in foreground")
      }
      
      // Show the query results
      VStack {
        Text("Available ToDos")
        ForEach(self.todos) {
          Text($0.title)
        }
      }
    }
  }
}

@ModelActor
actor BackgroundDataActor: ModelActor {
  
  func store(id: PersistentIdentifier) {
    print("Trying to save")
    print("Is auto save enabled: \(self.modelContext.autosaveEnabled)")
    
    if let dbo = self[id, as: ToDo.self] {
      self.modelContext.insert(dbo)
      
      
      try! self.modelContext.save()
      print("Saved into database")
    }
  }
}

Replies

Have a look at the following article: https://www.massicotte.org/isolation-intuition about Swift Isolation Intuition.

Perhaps it will help you understand where Swift runs your code.