When to use structs with swift data?

I am unsure the correct way to model my data.

I have a swift data object and then some referenced objects. Is there ever a reason those should just be structs or should they always be swift data objects themselves?

for example right now this is what I'm doing:

@Model
final class Exam {
    var timestamp: Date
    @Attribute(.unique) var examID: UUID
    var title: String
    var questions: [Question]
...
}

and Question is

struct Question: Codable, Identifiable {
    var id: UUID
    var number: Int
    var points: Int
    var prompt: String
    var answer: String
}

is there any problem with this or should I not be using a Struct for Question and instead use another Swift Data object with @Relationship ?

I thought since its a simple object just using a struct would be fine, however...

when I create a new Question object, it seems to create SwiftUI retain cycles with the warning === AttributeGraph: cycle detected through attribute 633984 === in the terminal

for example,

Button("Add Question", systemImage: "questionmark.diamond") {
    let newQuestion = Question(id: UUID(), number: exam.questions.count+1, points: 1, prompt: "", answer: "", type: .multipleChoice)
    exam.questions.append(newQuestion)
}

So, is it ok to mix structs with swift data objects or is it not best practice? And is this causing the SwiftUI retain cycles or are the issues unrelated?

Answered by ajmacleod in 794340022

I think your question should be a class if you want to persist it in SwiftData. I think the general advice (for SwiftData) is: structs are for views, classes are for models.

Your models would be something like this:

@Model
final class Exam {
  let timestamp: Date
  let id: String
  var title: String
  
  @Relationship(deleteRule: .cascade, inverse: \Question.exam) var questions: [Question] = []

  init(title:String = "") {
    self.timestamp = .now
    self.id = UUID().uuidString
    self.title = title
  }
}

@Model
final class Question {
  let id: String
  var number: Int
  var points: Int
  var prompt: String
  var answer: String

  @Relationship var exam: Exam? = nil

  init(
    number: Int = 0,
    points: Int = 0,
    prompt: String = "",
    answer: String = ""
  ) {
    self.id = UUID().uuidString
    self.number = number
    self.points = points
    self.prompt = prompt
    self.answer = answer
  }
}

I hope this helps!

Accepted Answer

I think your question should be a class if you want to persist it in SwiftData. I think the general advice (for SwiftData) is: structs are for views, classes are for models.

Your models would be something like this:

@Model
final class Exam {
  let timestamp: Date
  let id: String
  var title: String
  
  @Relationship(deleteRule: .cascade, inverse: \Question.exam) var questions: [Question] = []

  init(title:String = "") {
    self.timestamp = .now
    self.id = UUID().uuidString
    self.title = title
  }
}

@Model
final class Question {
  let id: String
  var number: Int
  var points: Int
  var prompt: String
  var answer: String

  @Relationship var exam: Exam? = nil

  init(
    number: Int = 0,
    points: Int = 0,
    prompt: String = "",
    answer: String = ""
  ) {
    self.id = UUID().uuidString
    self.number = number
    self.points = points
    self.prompt = prompt
    self.answer = answer
  }
}

I hope this helps!

Sorry for my delay, thank you, it makes sense. I wish Apple documentation was more clear and gave more explanations on not only the intended way to use an API like Swift Data but also why it is a certain way and also compared with incorrect usage.

When to use structs with swift data?
 
 
Q