What is SwiftData and how do you use it?
An excerpt from the 2nd edition of The iOS Interview Guide I'm currently working on.
Hello all, I’m in the mids of revising the storage layer chapter of the upcoming 2nd edition of The iOS Interview Guide. I’m just about done with one of the questions about SwiftData and figured I should post the excerpt, the entire question, here as a sneak peek.
This chapter covers all things storage, prepping you for most of the things related to storage you’d need in your applications or in an interview. The challenge is to keep every question/answer broad and deep enough to be helpful but not too deep to dive into unnecessary rabbit holes.
Let me know what you think - I’m open to all feedback. Without further ado:
What is SwiftData and how do you use it?
When you are asked in more detail how you would implement a database solution on iOS, it is a good idea to go for one of the built-in solutions such as Core Data or SwiftData. This question is likely asked to gauge your knowledge of the specifics of how to work with SwiftData.
Expected Answer: As mentioned in another question in this chapter, SwiftData is a syntactic sugar “nice” wrapper around Core Data. Under the hood, it uses the same technology for object graph and persistence as Core Data, but it’s just wrapped with a nicer “swifty” API.
That said, there are some limitations and missing features of Core Data that SwiftData lacks, such as:
child contexts
NSFetchedResultsController
some predicates
and more
The way SwiftData is implemented and how Apple intends you to use it (according to its documentation) is very UI-centric and couples you to SwiftUI a lot. Do yourself a favor and don’t follow Apple’s advice here - instead, treat this framework as you would any other 3rd party framework and abstract your code away from it. It will make it easier to switch out of it later and will allow you to avoid some of the headaches this kind of tight coupling comes with.
Specifically, it’s the @Query macro, @Environment(\.modelContext), modelContainer and other SwiftUI View extensions. These assume that your entire app is UI-view-layer-driven and couple your storage layer to your UI layer, which is a big architectural no-no.
Let’s expand on our previous PostsStroage example (which does not couple your UI to your storage layer) and refactor it to use SwiftData now for persistence:
import Foundation
import SwiftData
protocol PostsStorageInterface {
func savePost(_ post: Post) async
func retrievePost(_ id: Int) async -> Post?
}
struct Post {
let id: Int
let title: String
let body: String
}
@Model
final class PostModel {
var id: Int
var title: String
var body: String
init(id: Int, title: String, body: String) {
self.id = id
self.title = title
self.body = body
}
}
final class SwiftDataPostsStorage: PostsStorageInterface {
private let modelContext: ModelContext
init(modelContainer: ModelContainer) {
self.modelContext = ModelContext(modelContainer)
}
func savePost(_ post: Post) async {
let postModel = PostModel(id: post.id, title: post.title, body: post.body)
modelContext.insert(postModel)
do {
try modelContext.save()
} catch {
// handle error
}
}
func retrievePost(_ id: Int) async -> Post? {
let predicate = #Predicate<PostModel>{ $0.id == id }
let descriptor = FetchDescriptor<PostModel>(predicate: predicate)
do {
if let postModel = try modelContext.fetch(descriptor).first {
let post = Post(id: postModel.id,
title: postModel.title,
body: postModel.body)
return post
} else {
return nil
}
} catch {
// handle error
return nil
}
}
}
guard let modelContainer = try? ModelContainer(for: PostModel.self) else {
return false
}
let swiftDataPostsStorage: PostsStorageInterface = SwiftDataPostsStorage(
modelContainer: modelContainer
)
let post1 = Post(id: 123, title: "title1", body: "body1")
Task {
await swiftDataPostsStorage.savePost(post1)
let simpleResult = await swiftDataPostsStorage.retrievePost(123)
print(simpleResult)
}
Here we have the following objects at play:
ModelContainer is responsible for connecting with the database and actually persisting your models into the database as records. It can be initialized with a configuration to set where the data is stored, whether it should be read-only, in-memory only, etc. You normally initialize it with a list of persistence Model objects it handles.
ModelContext is responsible for being an “in-memory” cache so to speak, which you use to perform operations such as insert, delete, fetch, save. Operations performed on a context will eventually be executed in the model container automatically or you can trigger it manually by calling `save()` method on it.
PostModel is a new object type we need to introduce to work with SwiftData. It is necessary for serialization and data mapping between our domain model `Post` and the SwiftData database layer. It has an `@Model` macro that effectively sets the schema for its table under the hood (this is something you’d have to do a bit more manually in Core Data in the `xcdatamodeld` file).
Just like before we have our own application code intact: Post and PostsStorage/PostsStorageInterface remain (mostly) the same.
Notice, though, a slight adjustment we had to make to PostsStorageInterface and PostsStorage: We made both savePost and retrievePost methods async. This was done due to SwiftData’s ModelContext saving and retrieving data from the ModelContainer asynchronously.
This is a good practice in general anyway for any kind of storage read/write and it could’ve been applied to all the previous examples of the PostsStorage implementation, even simple in-memory array access.
Red Flag: Core Data and/or SwiftData are the go-to built-in database solutions. These days, every iOS developer needs to be familiar with them.
Thanks for reading. If you liked this excerpt could do me a favor? Could you also share this substack with your fellow iOS developers? It will help me a lot to grow this publication and get more feedback on the upcoming book!
Subscribe to my substack to hear more updates on the book’s progress like this one.
Or sign up for the waitlist on https://iosinterviewguide.com/ to only hear about big “official” book updates such as when pre order starts or when each chapter is updated and released (I’ll also post those updates here on The Mobile Engineer substack too).