Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
160: SwiftData Model Definitions
When you first start playing with SwiftData, it feels like magic. You just slap the @Model macro on a class, and suddenly your data persists across app launches. It's tempting to treat these models like regular Swift classes, but that's where most developers trip up. I've seen a lot of people build their initial schemas naively, only to realize three weeks later that their database is a mess of duplicate entries and orphaned records.
The "Just Make it Work" Model
Let's say we're building a personal library app. The naive approach is to define your models as simple containers. You'd probably create a Book and an Author class, link them with a simple property, and call it a day. It looks something like this:
@Model
class Author {
var name: String
var books: [Book]
init(name: String) {
self.name = name
self.books = []
}
}
@Model
class Book {
var title: String
var author: Author?
init(title: String) {
self.title = title
}
}
On the surface, this is fine. But here is where it breaks: data integrity. In this setup, if you add a book by "Stephen King" and then add another book by "Stephen King," you'll likely end up creating two separate Author objects with the same name. SwiftData doesn't automatically know that "Stephen King" in record A is the same person as "Stephen King" in record B. You've just introduced redundancy into your store, which makes searching, filtering, and updating your authors a nightmare.
Enforcing Uniqueness and Managing Lifecycles
To fix this, we need to move away from "simple containers" and start thinking about the model as a database schema. The first tool in your kit is the @Attribute(.unique) modifier. By marking a property—like an ISBN for a book or an email for a user—as unique, you tell SwiftData to perform an "upsert" (update or insert). If you try to insert a book with an ISBN that already exists, SwiftData will update the existing record instead of creating a duplicate.
Then there's the problem of "ghost" data. In the naive example above, if you delete an Author, what happens to their Book objects? By default, they might just hang around in the database with a null author reference. Depending on your app, that's either a memory leak of sorts or just useless clutter. I prefer to be explicit about these relationships using the @Relationship macro.
Here is how I would actually structure these models for a production app:
@Model
class Author {
@Attribute(.unique) var name: String
// If the author is deleted, we want their books deleted too
@Relationship(deleteRule: .cascade, inverse: \Book.author)
var books: [Book] = []
init(name: String) {
self.name = name
}
}
@Model
class Book {
@Attribute(.unique) var isbn: String
var title: String
var author: Author?
init(isbn: String, title: String) {
self.isbn = isbn
self.title = title
}
}
The Trade-off: Flexibility vs. Rigidity
You might wonder why we don't just make everything unique or cascade every delete. There's a cost. Unique constraints add a small amount of overhead because SwiftData has to check for existing values before every insert. More importantly, .cascade deletes are dangerous. If you accidentally delete a "Category" model that is linked via cascade to a thousand "Product" models, those products are gone instantly.
I usually suggest starting with .nullify (the default) unless you are absolutely certain that the child object cannot exist without the parent. In our library example, a book cannot logically exist without an author in our system's context, so .cascade makes sense. But if you were linking a Book to a Genre, you'd use .nullify—because deleting the "Sci-Fi" genre shouldn't wipe out every Sci-Fi book in your library.
📋 Practical Task
Implement a Unique Recipe and Ingredient Schema
You are building a Recipe Manager. Your goal is to create two SwiftData models, Recipe and Ingredient, that prevent data duplication and handle deletions cleanly.
- Create a
Recipemodel with atitleand arecipeID. TherecipeIDmust be unique to ensure no duplicate recipes are added to the store. - Create an
Ingredientmodel with anameandquantity. - Establish a relationship where one
Recipehas manyIngredients. - Configure the relationship so that if a
Recipeis deleted, all of its associatedIngredientsare also automatically deleted from the database. - Ensure the
Ingredientmodel has a reference back to itsRecipe.
There are no comments for now.