Skip to content
All essays
iOSMarch 27, 202516 min

Core Data Complete Guide: iOS Persistence

Master Core Data for iOS apps. Data models, queries, relationships, and advanced features.

Ü
Ümit Uz
Mobile & Full Stack Developer

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

  1. 1Context Management: Use appropriate context for operations
  2. 2Fetch Limits: Always limit fetch results when possible
  3. 3Predicates: Use predicates for filtering
  4. 4Batch Operations: Use batch operations for bulk updates
  5. 5Relationships: Define clear relationships
  6. 6Migration: Plan for data model changes
  7. 7Testing: Test persistence layer thoroughly
  8. 8Performance: Profile and optimize queries

Core Data provides a robust framework for data persistence in iOS apps!

Related essays

Next essay
Swift Charts Complete Guide: Data Visualization