I've already submitted this as a bug report to Apple, but I am posting here so others can save themselves some troubleshooting. This is submitted as FB14337982 with an attached complete Xcode project to replicate.
In iOS 17 we use a ModelActor to download data which is saved as an Event, and then save it to SwiftData with a relationship to a Location. In iOS 18 22A5307d we are seeing that this code no longer persists the relationship to the Location, but still saves the Event. If we put a breakpoint in that ModelActor we see that the object graph is correct within the ModelActor stack trace at the time we call modelContext.save(). However, after saving, the relationship is missing from the default.store SQLite file, and of course from the app UI.
Here is a toy example showing how inserting an Employee into a Company using a ModelActor gives unexpected results in iOS 18 22A5307d but works as expected in iOS 17.
It appears that no relationships data survives being saved in a ModelActor.ModelContext.
Also note there seems to be a return of the old bug that saving this data in the ModelActor does not update the @Query in the UI in iOS 18 but does so in iOS 17.
Models
@Model
final class Employee {
var uuid: UUID = UUID()
@Relationship(deleteRule: .nullify) public var company: Company?
/// For a concise display
@Transient var name: String {
self.uuid.uuidString.components(separatedBy: "-").first ?? "NIL"
}
init(company: Company?) {
self.company = company
}
}
@Model
final class Company {
var uuid: UUID = UUID()
@Relationship(deleteRule: .cascade, inverse: \Employee.company)
public var employees: [Employee]? = []
/// For a concise display
@Transient var name: String {
self.uuid.uuidString.components(separatedBy: "-").first ?? "NIL"
}
init() { }
}
ModelActor
import OSLog
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "SimpleModelActor")
@ModelActor
final actor SimpleModelActor {
func addEmployeeTo(CompanyWithID companyID: PersistentIdentifier?) {
guard let companyID,
let company: Company = self[companyID, as: Company.self] else {
logger.error("Could not get a company")
return
}
let newEmployee = Employee(company: company)
modelContext.insert(newEmployee)
logger.notice("Created employee \(newEmployee.name) in Company \(newEmployee.company?.name ?? "NIL")")
try! modelContext.save()
}
}
ContentView
import OSLog
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "View")
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var companies: [Company]
@Query private var employees: [Employee]
@State private var simpleModelActor: SimpleModelActor!
var body: some View {
ScrollView {
LazyVStack {
DisclosureGroup("Instructions") {
Text("""
Instructions:
1. In iOS 17, tap Add in View. Observe that an employee is added with Company matching the shown company name.
2. In iOS 18 beta (22A5307d), tap Add in ModelActor. Note that the View does not update (bug 1). Note in the XCode console that an Employee was created with a relationship to a Company.
3. Open the default.store SQLite file and observe that the created Employee does not have a Company relationship (bug 2). The relationship was not saved.
4. Tap Add in View. The same code is now executed in a Button closure. Note in the XCode console again that an Employee was created with a relationship to a Company. The View now updates showing both the previously created Employee with NIL company, and the View-created employee with the expected company.
""")
.font(.footnote)
}
.padding()
Section("**Companies**") {
ForEach(companies) { company in
Text(company.name)
}
}
.padding(.bottom)
Section("**Employees**") {
ForEach(employees) { employee in
Text("Employee \(employee.name) in company \(employee.company?.name ?? "NIL")")
}
}
Button("Add in View") {
let newEmployee = Employee(company: companies.first)
modelContext.insert(newEmployee)
logger.notice("Created employee \(newEmployee.name) in Company \(newEmployee.company?.name ?? "NIL")")
try! modelContext.save()
}
.buttonStyle(.bordered)
Button("Add in ModelActor") {
Task {
await simpleModelActor.addEmployeeTo(CompanyWithID: companies.first?.persistentModelID)
}
}
.buttonStyle(.bordered)
}
}
.onAppear {
simpleModelActor = SimpleModelActor(modelContainer: modelContext.container)
if companies.isEmpty {
let newCompany = Company()
modelContext.insert(newCompany)
try! modelContext.save()
}
}
}
}