SwiftData initializing Optional Array to Empty Array

I've been seeing something that I find odd when using two SwiftData models where if I have one model (book, in this case) that has an optional array of another model (page, in this case), the optional array starts out as set to nil, but after about 20 seconds it updates to being an empty array.

I see it in Previews and after building.

Is this expected behavior? Should I just assume that if there is an optional array in my model it will eventually be initialized to an empty array?

Code is below.

import SwiftUI
import SwiftData

@Model
final class Book {
    var title: String = "New Book"
    @Relationship var pages: [Page]? = nil
    
    init(title: String) {
        self.title = title
    }
}

@Model
final class Page {
    var content: String = "Page Content"
    var book: Book? = nil
    
    init() {
        
    }
}

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var books: [Book]

    var body: some View {
        NavigationSplitView {
            List {
                ForEach(books) { book in
                    NavigationLink {
                        Text("\(book.title)")
                        Text(book.pages?.debugDescription ?? "pages is nil")
                    } label: {
                        Text("\(book.title)")
                        Spacer()
                        Text("\(book.pages?.count.description ?? "pages is nil" )")
                    }
                }
            }
            HStack {
                Button("Clear Data") {
                    clearData()
                }
                Button("Add Book") {
                    addBook()
                }
            }
            .navigationSplitViewColumnWidth(min: 180, ideal: 200)
        } detail: {
            Text("Select an item")
        }

    }
    private func clearData() {
        for book in books {
            modelContext.delete(book)
        }
        try? modelContext.save()
    }
    private func addBook() {
        let newBook = Book(title: "A New Book")
        modelContext.insert(newBook)
    }

}

@main
struct BookPageApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([Book.self, Page.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()
        }
        .modelContainer(sharedModelContainer)
    }
}


#Preview {
    ContentView()
        .modelContainer(for: Book.self, inMemory: true)
}
Answered by DTS Engineer in 854950022

What you observed is the behavior that the framework currently has – If you save the model context right after adding a book, you will see that the relationship turns from nil to [] immediately, and that's because SwiftData makes the change when persisting the model.

SwiftData does that because the default store (DefaultStore) is based on Core Data, and Core Data expresses an empty toMany relationship with an empty set. (Core Data had been implemented with Objective C long before Swift was introduced.)

Having said that, if you have a different opinion about the behavior, feel free to file a feedback report – If you do so, please share your report ID here. Thanks.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Accepted Answer

What you observed is the behavior that the framework currently has – If you save the model context right after adding a book, you will see that the relationship turns from nil to [] immediately, and that's because SwiftData makes the change when persisting the model.

SwiftData does that because the default store (DefaultStore) is based on Core Data, and Core Data expresses an empty toMany relationship with an empty set. (Core Data had been implemented with Objective C long before Swift was introduced.)

Having said that, if you have a different opinion about the behavior, feel free to file a feedback report – If you do so, please share your report ID here. Thanks.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

SwiftData initializing Optional Array to Empty Array
 
 
Q