Core Data is Apple's framework for managing the model layer of your application. Learn to build robust data persistence with Core Data.
Core Data Stack Setup
Basic Setup
swift
import CoreData
import SwiftUI
class PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "DataModel")
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Core Data store failed to load: \(error)")
}
}
}
var viewContext: NSManagedObjectContext {
container.viewContext
}
func save() {
let context = viewContext
if context.hasChanges {
do {
try context.save()
} catch {
print("Failed to save context: \(error)")
}
}
}
}App Integration
swift
@main
struct MyApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}Data Model
Entity Definition
Create entities in the .xcdatamodeld file:
User Entity:
- Attributes: id (String), name (String), email (String), createdAt (Date)
- Relationships: posts (to-many), settings (to-one)
Post Entity:
- Attributes: id (String), title (String), content (String), createdAt (Date)
- Relationships: author (to-one), comments (to-many)
CRUD Operations
Create
swift
extension PersistenceController {
func createUser(name: String, email: String) -> User {
let user = User(context: viewContext)
user.id = UUID().uuidString
user.name = name
user.email = email
user.createdAt = Date()
save()
return user
}
func createPost(title: String, content: String, for user: User) -> Post {
let post = Post(context: viewContext)
post.id = UUID().uuidString
post.title = title
post.content = content
post.createdAt = Date()
post.author = user
save()
return post
}
}Read
swift
extension PersistenceController {
func fetchUsers() -> [User] {
let request: NSFetchRequest<User> = User.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
do {
return try viewContext.fetch(request)
} catch {
print("Failed to fetch users: \(error)")
return []
}
}
func fetchUser(id: String) -> User? {
let request: NSFetchRequest<User> = User.fetchRequest()
request.predicate = NSPredicate(format: "id == %@", id)
request.fetchLimit = 1
do {
return try viewContext.fetch(request).first
} catch {
print("Failed to fetch user: \(error)")
return nil
}
}
func searchUsers(searchText: String) -> [User] {
let request: NSFetchRequest<User> = User.fetchRequest()
request.predicate = NSPredicate(
format: "name CONTAINS[cd] %@ OR email CONTAINS[cd] %@",
searchText, searchText
)
do {
return try viewContext.fetch(request)
} catch {
print("Failed to search users: \(error)")
return []
}
}
}Update
swift
extension PersistenceController {
func updateUser(_ user: User, name: String? = nil, email: String? = nil) {
if let name = name {
user.name = name
}
if let email = email {
user.email = email
}
save()
}
}Delete
swift
extension PersistenceController {
func deleteUser(_ user: User) {
viewContext.delete(user)
save()
}
func deleteAllUsers() {
let request: NSFetchRequest<NSFetchRequestResult> = User.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
do {
try viewContext.execute(deleteRequest)
} catch {
print("Failed to delete all users: \(error)")
}
}
}NSFetchRequest
Complex Queries
swift
extension PersistenceController {
func fetchRecentPosts(limit: Int = 10) -> [Post] {
let request: NSFetchRequest<Post> = Post.fetchRequest()
request.fetchLimit = limit
request.sortDescriptors = [
NSSortDescriptor(key: "createdAt", ascending: false)
]
do {
return try viewContext.fetch(request)
} catch {
print("Failed to fetch recent posts: \(error)")
return []
}
}
func fetchPostsByUser(_ user: User) -> [Post] {
let request: NSFetchRequest<Post> = Post.fetchRequest()
request.predicate = NSPredicate(format: "author == %@", user)
request.sortDescriptors = [
NSSortDescriptor(key: "createdAt", ascending: false)
]
do {
return try viewContext.fetch(request)
} catch {
print("Failed to fetch posts: \(error)")
return []
}
}
func fetchPostsWithComments() -> [Post] {
let request: NSFetchRequest<Post> = Post.fetchRequest()
request.predicate = NSPredicate("comments.@count > 0")
request.sortDescriptors = [
NSSortDescriptor(key: "createdAt", ascending: false)
]
do {
return try viewContext.fetch(request)
} catch {
print("Failed to fetch posts: \(error)")
return []
}
}
}Background Context
Background Operations
swift
extension PersistenceController {
func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
container.performBackgroundTask { context in
block(context)
}
}
func importUsersBatch(_ usersData: [[String: Any]]) {
performBackgroundTask { context in
for userData in usersData {
let user = User(context: context)
user.id = userData["id"] as? String ?? UUID().uuidString
user.name = userData["name"] as? String ?? ""
user.email = userData["email"] as? String ?? ""
user.createdAt = Date()
}
do {
try context.save()
} catch {
print("Failed to save batch: \(error)")
}
}
}
}SwiftUI Integration
@FetchRequest
swift
struct UserListView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)],
animation: .default)
private var users: FetchedResults<User>
var body: some View {
List {
ForEach(users) { user in
NavigationLink(destination: UserDetailView(user: user)) {
UserRow(user: user)
}
}
.onDelete(perform: deleteUsers)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: addUser) {
Label("Add User", systemImage: "plus")
}
}
}
}
private func addUser() {
withAnimation {
let newUser = User(context: viewContext)
newUser.id = UUID().uuidString
newUser.name = "New User"
newUser.email = "new@example.com"
newUser.createdAt = Date()
do {
try viewContext.save()
} catch {
print("Failed to save: \(error)")
}
}
}
private func deleteUsers(offsets: IndexSet) {
withAnimation {
offsets.map { users[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
print("Failed to delete: \(error)")
}
}
}
}
struct UserRow: View {
@ObservedObject var user: User
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(user.name ?? "Unknown")
.font(.headline)
Text(user.email ?? "No email")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}Data Migration
Lightweight Migration
swift
class PersistenceController {
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "DataModel")
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.persistentStoreDescriptions.forEach { description in
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
}
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Core Data store failed to load: \(error)")
}
}
}
}Best Practices
- 1Context Management: Use appropriate context for operations
- 2Fetch Limits: Always limit fetch results when possible
- 3Predicates: Use predicates for filtering
- 4Batch Operations: Use batch operations for bulk updates
- 5Relationships: Define clear relationships
- 6Migration: Plan for data model changes
- 7Testing: Test persistence layer thoroughly
- 8Performance: Profile and optimize queries
Core Data provides a robust framework for data persistence in iOS apps!