Object oriented programming, at its core, provides two kinds of relationships between objects: “is-a” and “contains”. Before you learn about using model inheritance in your SwiftData app, here’s a quick refresher on whether you need to use inheritance in the first place. Open the starter project for this lesson to code along.
The “contains” relationship is usually called “Composition” and is where you compose objects (classes or structs) out of other objects. There are usually no (or very few) common properties between the objects. In the starter project, add a new Swift file to the Models folder called Cookbook. This class would be composed of, among other things, a collection of Recipe objects:
class Cookbook{
var title: String
var releaseDate: Date
var recipes: [Recipe]
init(title: String = "New Recipe", releaseDate: Date = Date(),
recipes: [Recipe] = []) {
self.title = title
self.releaseDate = releaseDate
self.recipes = recipes
}
}
As with the Recipe class, add a @Model macro to get this ready for SwiftData. This compositional relationship has been available since SwiftData was released back in iOS 17, and is noted with the @Relationship macro. Add that macro and give it a deleteRule: .cascade.
import Foundation
import SwiftData
@Model
class Cookbook{
var title: String
var releaseDate: Date
@Relationship(deleteRule: .cascade)
var recipes: [Recipe]
init(title: String = "New Recipe", releaseDate: Date = Date(),
recipes: [Recipe] = []) {
self.title = title
self.releaseDate = releaseDate
self.recipes = recipes
}
}
You’ll notice that a similar relationship was provided between Recipe models and Ingredient models in the last lesson.
The only thing the Cookbook model and the Recipe model have in common is that the Cookbook is composed of Recipes. There are no shared properties or functions.
The “is-a” relationship, or “Inheritance”, allows for the sharing of properties and functions between parent objects and their children. Here’s the Recipe model from the last lesson:
@Model
class Recipe {
@Attribute(.unique)
var name: String
var summary: String = ""
var instructions: String = ""
@Relationship(deleteRule: .cascade)
var ingredients: [Ingredient]
init(name: String, summary: String = "", instructions: String = "",
ingredients: [Ingredient] = []) {
self.name = name
self.summary = summary
self.instructions = instructions
self.ingredients = ingredients
}
//sample data here...
}
This base recipe class is pretty, well, basic. A name, a summary, instructions, and a list of ingredients. There are lots of different types of recipes out there in the cooking world, so you could get a little more specific. You’ll create a class called BakedGood that extends from Recipe, and provide a panSize property. This is is an enum, PanSize. First, create a new Swift file and called it PanSize.
import Foundation
enum PanSize: Codable {
case small
case medium
case large
}
Now for the BakedGood class. Create a new Swift file and call it BakedGood. Update it to the following:
import Foundation
import SwiftData
class BakedGood: Recipe {
var panSize: PanSize
init(name: String, summary: String = "", instructions: String = "",
ingredients: [Ingredient] = [], panSize: PanSize = .medium) {
self.panSize = panSize
super.init(name: name, summary: summary, instructions: instructions,
ingredients: ingredients)
}
required init(backingData: any SwiftData.BackingData<Recipe>) {
fatalError("init(backingData:) has not been implemented")
}
}
You might also want to capture the recipes for your favorite seasonal beverage. Create a new Beverage class, which also extends from Recipe but has two unique properties: season, an enum for which season this beverage is perfect for, and a bool describing whether the beverage is caffeinated or not. First add a new file called Season.swift. It should look like the following:
import Foundation
enum Season: Codable {
case spring
case summer
case autumn
case winter
}
Next, create a new swift file called Beverage.swift. It should look like the following:
import Foundation
import SwiftData
class Beverage: Recipe {
var season: Season // a summer drink or a winter drink?
var caffienated: Bool
init(name: String, summary: String = "", instructions: String = "",
ingredients: [Ingredient] = [], season: Season, caffienated: Bool) {
self.season = season
self.caffienated = caffienated
super.init(name: name, summary: summary, instructions: instructions,
ingredients: ingredients)
}
required init(backingData: any SwiftData.BackingData<Recipe>) {
fatalError("init(backingData:) has not been implemented")
}
}
As with the other @Models from earlier, macros are needed so SwiftData can recognize these as persistent models. Add those macros, but be prepared for some errors. In BakedGood.swift:
@Model
class BakedGood: Recipe {
///....
}
And in Beverage.swift:
@Model
class Beverage: Recipe {
///....
}
Once you add this code, the compiler shows errors. The ability to inherit a SwiftData @Model is restricted to iOS 26, so you need to specify platform availability. Add available macros to both BakedGood.swift:
@available(iOS 26, *)
@Model
class BakedGood: Recipe {
///....
}
And Beverage.swift:
@available(iOS 26, *)
@Model
class Beverage: Recipe {
///....
}
This of course means that older version of iOS won’t be able to support the new child @Models, so if you are going to support older versions of iOS, you’ll need to handle this all over your code!
These two classes, BakedGood and Beverage extend the existing Recipe type, inheriting the properties of the parent. They also add in class specific properties of their own that the base Receipe class doesn’t have.
The questions remains, however: do you need to use inheritance? Part of the answer lies in how the classes will be used in your app. If you never explicitly use the child classes by only referring to generic recipes when you perform queries then subclasses might not be needed. You could specify their differences in a shared property like the summary or the instructions. Conversely, if you only use the child classes, and don’t have many of them, those shared properties from the Recipe class just could be repeated in the child classes.
If you construct your app where you use both the parent and child classes, then inheritance will do the trick. In the next segment, you’ll learn how you can do that.
See forum comments
This content was released on Dec 10 2025. The official support period is 6-months
from this date.
A quick refresher on inheritance and composition in object oriented programming, and how it works with SwiftData.
Download course materials from Github
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.