Search code examples
swiftswiftdataswift-data-relationship

How do I handle non-optional one-to-many relationships in SwiftData?


I have a one-to-many relationship between two models:

import SwiftData

@Model
final class TrackedTask {
  @Relationship(
    deleteRule: .deny,  // can't use .cascade with a non-optional 1:N relationship
    inverse: \TaskEntry.task
  )
  var entries = [TaskEntry]()
}


@Model
final class TaskEntry {
  @Relationship var task: TrackedTask
}

Ideally I would use deleteRule: .cascade, but this requires me to specify task as optional, which just seems wrong to me, since a TaskEntry should always have an associated task. Is the canonical thing to do just accept that TaskEntry.task has to be optional?

I was hoping to use explicit error handling like so:

// ...in a SwiftUI view...
do {
  try modelContext.save()
} catch {
  print("\(error)")
}

But, presumably due to multithreading shenanigans, the fatal error thrown cannot be caught in this statement and crashes the app. Is there a way of ensuring that a record that has associated records can't be deleted without crashing the app?

In practice, I will probably just remove the ability to delete tasks and just use a kind of soft delete instead. But it would be interesting to get an answer to this either way as I haven't found one anywhere online.


Solution

  • An optional Relationship is actually “right” for a number of reasons starting with that it is the default for CoreData so a lot of features require it but most important

    CloudKit requires all relationships to be optional

    https://developer.apple.com/documentation/swiftdata/syncing-model-data-across-a-persons-devices

    Which tends to be the default since syncing between devices and iCloud backup is a positive feature.

    Also, this property becomes a little less relevant if you are using SwiftUI because you will want to use FetchRequest so you can observe changes to the array