Formation PUB900 : Développer une application pour iPhone avec SwiftUI, H-2024 SwiftData

37.5 Définir des relations entre les tables


Pour créer des tables liées par des clés étrangères, il faut ajouter au modèle de la table enfant une référence au modèle de la table parent. Dans une relation de un à plusieurs, il s'agira d'une propriété dont le type correspond à la table parent.

Pour que SwiftData puisse faire correctement le lien entre la table parent et la table enfant, cette propriété doit être un optionnel, c'est-à-dire qu'elle peut avoir la valeur nil, tel qu'indiqué par le point d'interrogation (ex : var categorie: Categorie?).

Inversement, le modèle de la table parent aura une référence au modèle de la table enfant. Toujours dans une relation de un à plusieurs, il s'agira d'un tableau. De plus, il doit pouvoir prendre la valeur nil. Pour que SwiftData puisse établir la relation, l'initialisation du tableau dans init() n'est pas suffisant. Il faut l'initialiser dès sa déclaration (ex : var items: [Item]? = []).

Si le tableau ne peut pas prendre la valeur nil, le programme plantera si vous videz complètement la table enfant (try container.mainContext.delete(model: Item.self)). Le tableau pouvant être nil est également requis si vous voulez synchroniser les données dans iCloud.

Remarquez que SwiftData se charge de créer les clés étrangères requises mais il ne crée pas de contraintes d'intégrité référentielle dans la base de données car il gère lui-même l'intégrité des données via son modèle objet.

Fichier Models/Item.swift

@Model
final class Item {
  var code: String
  var titre: String
  var categorie: Categorie?

  // Ce constructeur sans paramètre pour la clé étrangère est requis si on veut insérer des données initiales.
  init(code: String = "", titre: String = "") {
    self.code = code
    self.titre = titre
  }

  init(code: String = "", titre: String = "", categorie: Categorie) {
    self.code = code
    self.titre = titre
    self.categorie = categorie
  }
}

Fichier Models/Categorie.swift

@Model
final class Categorie {
  var titre: String
  var items: [Item]? = []

  // Ce constructeur avec paramètre pour la liste d'items est requis si on veut insérer des données initiales.
  // Puisque le paramètre a une valeur par défaut, il sera tout de même possible de créer une catégorie sans paramètre pour les items.
  init(titre: String = "", items: [Item] = []) {
    self.titre = titre
    self.items = items
  }
}

Puisqu'il y a une relation entre les deux modèles, le modelContainer n'a besoin que du nom d'une des classes. Il sera capable de retrouver l'autre par lui-même.

Fichier MonProjetApp.swift

@main
struct MonProjetApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .modelContainer(for: Item.self)
  }
}

Attention : Dans le modèle de la table parent, le tableau de modèles de la table enfant doit absolument être assigné en dernier.

Si vous ne respectez pas cette condition, la relation ne sera pas correctement établie entre les tables.

Swift

@Model
final class Categorie {
  var titre: String
  var items: [Item]? = []

  init(titre: String = "", items: [Item] = []) {
    self.items = items   // cette ligne doit être exécutée en dernier pour que la relation soit bien établie
    self.titre = titre
  }
}

Relation explicite avec la macro @Relationship

Il n'est pas nécessaire d'en faire plus pour que SwiftData soit capable de gérer la relation entre les deux tables : une table parent avec un tableau et une table enfant avec une propriété optionnelle qui assure que les données ne seront jamais corrompues lors d'une suppression (un item qui pointe sur une catégorie qui n'existe plus recevra automatiquement la valeur nil dans sa propriété).

Néanmoins, il est conseillé d'ajouter la macro @Relationship dans la table parent afin de rendre la relation explicite.

De plus, ceci permettre d'indiquer clairement ce qui se passera avec les enregistrements de la table enfant si un enregistrement de la table parent est supprimé.

La macro sera placée sur la même ligne ou au-dessus du tableau d'items.

Dans cet exemple, si on détruit une catégorie, tous les items de cette catégorie se verront attribuer la valeur nil dans la propriété categorie.

Fichier Models/Categorie.swift

@Model
final class Categorie {
  var titre: String
  @Relationship(deleteRule: .nullify, inverse: \Item.categorie)
  var items: [Item]? = []
  ...
}

Dans cet exemple, si on détruit une catégorie, tous les items de cette catégorie seront également supprimés

Fichier Models/Categorie.swift

@Model
final class Categorie {
  var titre: String
  @Relationship(deleteRule: .cascade, inverse: \Item.categorie)
  var items: [Item]? = []
  ...
} 

Pour plus d'information

« Defining data relationships with enumerations and model classes ». Apple. https://developer.apple.com/documentation/swiftdata/defining-data-relationships-with-enumerations-and-model-classes/

« The Ultimate Guide to Building SwiftData Applications - Relationships ». AzamSharp. https://azamsharp.com/2023/07/04/the-ultimate-swift-data-guide.html#relationships

« Inferred vs explicit relationships ». Hacking with Swift. https://www.hackingwithswift.com/quick-start/swiftdata/inferred-vs-explicit-relationships

▼Publicité

Veuillez noter que le contenu de cette fiche vous est partagé à titre gracieux, au meilleur de mes connaissances et sans aucune garantie.
Merci de partager !
Soumettre