Section 7/181 menit

7. Relasi Antar Model

7. Relasi Antar Model

SwiftData mendukung relasi one-to-one, one-to-many, dan many-to-many dengan @Relationship.

One-to-Many

swift
@Model
final class Author {
    var name: String
    var biography: String

    // One Author → many Books
    @Relationship(deleteRule: .cascade, inverse: \Book.author)
    var books: [Book] = []

    init(name: String, biography: String = "") {
        self.name = name
        self.biography = biography
    }
}

@Model
final class Book {
    var title: String
    var publishedYear: Int

    // Many Books → one Author (optional karena buku bisa orphan)
    var author: Author?

    init(title: String, publishedYear: Int) {
        self.title = title
        self.publishedYear = publishedYear
    }
}

Delete Rules

Delete Rule Perilaku
.nullify (default) Set relasi ke nil saat parent dihapus
.cascade Hapus semua child saat parent dihapus
.deny Tolak penghapusan parent jika masih punya child
.noAction Tidak ada aksi — developer bertanggung jawab

Many-to-Many

swift
@Model
final class Book {
    var title: String

    @Relationship(inverse: \Tag.books)
    var tags: [Tag] = []

    init(title: String) {
        self.title = title
    }
}

@Model
final class Tag {
    var name: String
    var color: String

    @Relationship(inverse: \Book.tags)
    var books: [Book] = []

    init(name: String, color: String = "blue") {
        self.name = name
        self.color = color
    }
}

// Penggunaan
let book = Book(title: "Clean Code")
let tag = Tag(name: "Programming")
context.insert(book)
context.insert(tag)
book.tags.append(tag)  // otomatis update tag.books juga
try context.save()

Relasi Nested dan Lazy Loading

SwiftData menggunakan lazy loading untuk relasi — data relasi hanya diambil dari disk saat property diakses pertama kali. Ini efisien untuk graph model yang besar.

swift
@Model
final class Department {
    var name: String

    @Relationship(deleteRule: .cascade, inverse: \Employee.department)
    var employees: [Employee] = []   // tidak di-load sampai diakses

    var headCount: Int { employees.count }   // akses ini trigger lazy load

    init(name: String) { self.name = name }
}