SwiftData synced to iCloud with one-to-many relationship between 2 models results in a crash when each model contains a reference to the other

I’m having an issue when two of my SwiftData models have a one-to-many relationship and I have them synced via CloudKit.

To be clear, I’ve met all of the requirements to make it iCloud friendly and sync to work. I followed this https://www.hackingwithswift.com/quick-start/swiftdata/how-to-sync-swiftdata-with-icloud, and can confirm I’ve done it correctly because initially I was seeing this crash on startup when I had not:

Thread 1: Fatal error: Could not create ModelContainer: SwiftDataError(_error: SwiftData.SwiftDataError._Error.loadIssueModelContainer)

This is to say, the problem may be iCloud related but it’s not due to a wrong model setup. Speaking of which, these are models:

@Model
class Film {
    var name: String = ""
    var releaseYear: Int = 0
    var director: Director? = nil

    init(name: String, releaseYear: Int, director: Director) {
        self.name = name
        self.releaseYear = releaseYear
        self.director = director
    }
}

@Model
class Director {
    var name: String = ""
    
    @Relationship(deleteRule: .cascade, inverse: \Film.director)
    var films: [Film]? = []

    init(name: String, films: [Film]) {
        self.name = name
        self.films = films
    }
}

I’ve set the delete rule for the relationship between Film and Director to be cascading because you can’t have a film without a director (to be clear, even when set as nullify, it doesn’t make a difference)

And this is the @main App definition:

@main
struct mvpApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Film.self,
            Director.self
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

    do {
        return try ModelContainer(for: schema, configurations: [modelConfiguration])
    } catch {
        fatalError("Could not create ModelContainer: \(error)")
    }
}()

var body: some Scene {
    WindowGroup {
        ContentView()
    }
}

And this is the dummy ContentView:

struct ContentView: View {
    var body: some View {
        EmptyView()
            .onAppear {
                let newDirector = Director(name: "Martin Scorcese", films: [])
                let film = Film(name: "The Wolf of Wall Street", releaseYear: 2019, director: newDirector)
                newDirector.films!.append(film)
            }
    }
}

I create a Director with no films assigned. I then create a Film, and the append it to the Director’s [Film] collection.

The last step however causes a crash consistently:

There is a workaround that involves removing this line from the Film init():

self.director = director // comment this out so it’s not set in a Film’s init()

When I do this, I can append the (Director-less) Film to the Director’s [Film] collection.

Am I misunderstanding how these relationships should work in SwiftData/CloudKit? It doesn’t make any sense to me that when two models are paired together that only one of them has a reference to the relationship, and the other has no knowledge of the link.

The above is a minimum reproducible example (and not my actual application). In my application, I basically compromised with the workaround that initially appears to be without consequence, but I have begun to notice crashes happening semi-regularly when deleting models that I suspect must be linked to setting the foundations incorrectly.

Replies

hi,

my guess on this is that you never inserted either model into the modelContext; linking them together either by appending the film to the director's films, or setting the film's director reference would cause an issue.

as to which you should do in linking objects together, it used to be simple in Core Data: you could do either (and the other direction would bee handled automatically).

however, for at least the early betas and the official release of Xcode 15 and iOS 17, it appeared to me that you were better off writing newDirector.films?.append(film) than film.director = newDirector, because the former was more likely to work with observation. (that could be fixed by now).

finally, setting self.director = director in a Film's init() is probably a bad thing to do; i'd want to insert a new Film into the modelContext before setting the director reference.

hope that helps,

DMG