Skip to main content

Working with Core Data

1. Setting Up Core Data Stack

Example: Basic Core Data Stack Setup

import CoreData

class CoreDataManager {
    static let shared = CoreDataManager()
    
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "MyModel")
        container.loadPersistentStores { _, error in
            if let error = error {
                fatalError("Core Data error: \(error)")
            }
        }
        return container
    }()
    
    var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
}
Component Description Notes
NSPersistentContainer Manages the persistent store and context Created with a model name
NSManagedObjectContext Handles changes to managed objects Used for read/write operations
NSPersistentStoreCoordinator Coordinates between store and managed objects Part of the Core Data stack
Note: The model name must match your .xcdatamodeld file.
Warning: Always handle errors in loadPersistentStores closure.

Setup Steps

  • Create a CoreDataManager singleton
  • Initialize NSPersistentContainer with model name
  • Load persistent stores in the container
  • Access managed object context via persistentContainer.viewContext

2. Creating Managed Object Model

Managed Object Models (MOM) define the structure of your Core Data entities and their relationships. You can create a MOM in two ways: programmatically or using the Core Data model editor.

Approach Description Use Case
Programmatic Define entities, attributes, and relationships using Swift code. Dynamic model generation or runtime configuration.
Model Editor Use Xcode's visual editor to define entities and relationships. Static, predefined models with visual design support.

Example: Programmatic MOM Creation

import CoreData

let model = NSManagedObjectModel()
let entity = NSEntityDescription()
entity.name = "Person"
entity.managedObjectClassName = "Person"

let nameAttribute = NSAttributeDescription()
nameAttribute.name = "name"
nameAttribute.attributeType = .stringAttributeType
nameAttribute.isOptional = false

let ageAttribute = NSAttributeDescription()
ageAttribute.name = "age"
ageAttribute.attributeType = .integer16AttributeType
ageAttribute.isOptional = true

entity.properties = [nameAttribute, ageAttribute]
model.entities = [entity]

let persistentContainer = NSPersistentContainer(name: "MyModel", managedObjectModel: model)
Note: When using the model editor, Xcode generates a .xcdatamodeld file that contains the model definition.
Warning: Ensure that all entities and their properties are correctly defined to avoid runtime errors when fetching or saving data.

3. Using NSManagedObjectContext

NSManagedObjectContext is the primary interface for working with Core Data. It manages a collection of managed objects and tracks changes to them.

Property Type Description
persistentStoreCoordinator NSPersistentStoreCoordinator? The coordinator that manages the persistent stores.
parent NSManagedObjectContext? The parent context, if this context is a child context.
concurrencyType NSManagedObjectContextConcurrencyType The concurrency type of the context (e.g., MainQueueConcurrencyType, PrivateQueueConcurrencyType).
name String? A name for the context, useful for debugging.

Example: Creating a Managed Object Context

let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.persistentStoreCoordinator = persistentStoreCoordinator

Example: Fetching Objects

let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
do {
    let results = try context.fetch(fetchRequest)
    for person in results {
        print(person.value(forKey: "name") as? String ?? "")
    }
} catch {
    print("Fetch failed: \(error)")
}

Example: Saving Changes

do {
    try context.save()
    print("Changes saved successfully")
} catch {
    print("Save failed: \(error)")
}
Note: Always handle errors when saving or fetching data to prevent crashes.
Warning: Changes made in a context are not persisted until save() is called.
Method Description Return Type
fetch(_:) Fetches managed objects from the persistent store. [NSManagedObject]
save() Saves changes to the persistent store. Void
insertObject(_:) Inserts a new managed object into the context. Void
delete(_:) Marks a managed object for deletion. Void

4. Creating Entities Programmatically

Entities in Core Data can be created programmatically using the NSEntityDescription and NSAttributeDescription classes. This approach is useful for dynamically generating entities at runtime.

Property Type Description
name String The name of the entity.
managedObjectClassName String The class name of the managed object subclass.
attributes [NSAttributeDescription] List of attributes for the entity.

Example: Creating an Entity Programmatically

import CoreData

let entity = NSEntityDescription()
entity.name = "Person"
entity.managedObjectClassName = "Person"

let nameAttribute = NSAttributeDescription()
nameAttribute.name = "name"
nameAttribute.attributeType = .stringAttributeType
nameAttribute.isOptional = false

let ageAttribute = NSAttributeDescription()
ageAttribute.name = "age"
ageAttribute.attributeType = .integer16AttributeType
ageAttribute.isOptional = true

entity.attributes = [nameAttribute, ageAttribute]

let managedObjectModel = NSManagedObjectModel()
managedObjectModel.entities = [entity]

5. Fetching Data

Example: Basic Fetch Request

let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
let context = persistentContainer.viewContext
let results = try context.fetch(fetchRequest)
Property Description Values
entity The entity to fetch NSFetchRequest<Person>
predicate Filter conditions for the fetch NSPredicate(format: "age >= 18")
sortDescriptors Sorting order for results [NSSortDescriptor(key: "name", ascending: true)]
fetchLimit Maximum number of results to return 10
Note: Always handle the try block when fetching data to avoid runtime crashes.

Example: Fetch with Predicate

let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name CONTAINS[cd] %@", "John")
let context = persistentContainer.viewContext
let results = try context.fetch(fetchRequest)

Example: Fetch with Sort

let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "age", ascending: false)]
let context = persistentContainer.viewContext
let results = try context.fetch(fetchRequest)

6. Filtering with Predicates

Filter Type Description Example
NSPredicate Used to filter Core Data fetch requests let predicate = NSPredicate(format: "name CONTAINS[cd] %@", searchTerm)
format String-based predicate format NSPredicate(format: "age > %d AND name BEGINSWITH %@", 18, "J")
keyPath Property to filter on NSPredicate(format: "department.name == %@", "Engineering")

Example: Fetching Employees by Department

let context = persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Employee> = Employee.fetchRequest()
let predicate = NSPredicate(format: "department.name == %@", "Engineering")
fetchRequest.predicate = predicate

do {
    let employees = try context.fetch(fetchRequest)
    print("Found \(employees.count) employees")
} catch {
    print("Fetch failed: \(error)")
}
let searchTerm = "john"
let predicate = NSPredicate(format: "name CONTAINS[cd] %@", searchTerm)

let fetchRequest: NSFetchRequest<Employee> = Employee.fetchRequest()
fetchRequest.predicate = predicate
Note: The [cd] modifier in predicates stands for case and diacritic insensitive matching.
Warning: Predicate format strings must be carefully validated to avoid runtime errors from malformed expressions.

7. Sorting Fetch Results

Property Description Usage
sortDescriptors Array of NSSortDescriptor objects defining how to sort the fetched results let sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]

Example: Sorting by Name Ascending

let request = NSFetchRequest<Employee>()
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]

Example: Sorting by Multiple Fields

let sortDescriptors = [
    NSSortDescriptor(key: "department", ascending: true),
    NSSortDescriptor(key: "name", ascending: false)
]
request.sortDescriptors = sortDescriptors
Note: Sort descriptors are applied in the order they appear in the array.

8. Updating Managed Objects

Property Description Notes
inserted Indicates if the object was newly inserted into the context NEW in iOS 17
updated Indicates if the object was modified in the context Available since iOS 13
deleted Indicates if the object was marked for deletion Available since iOS 13

Example: Updating a Managed Object

let context = persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name == %@", "John")

do {
    let results = try context.fetch(fetchRequest)
    if let person = results.first {
        person.name = "John Smith"
        try context.save()
    }
} catch {
    print("Failed to update person: \(error)")
}

Example: Using NSFetchedResultsController for Updates

class PersonTableViewController: UITableViewController {
    lazy var fetchedResultsController: NSFetchedResultsController<Person> = {
        let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        
        let frc = NSFetchedResultsController(
            fetchRequest: fetchRequest,
            managedObjectContext: context,
            sectionNameKeyPath: nil,
            cacheName: nil
        )
        frc.delegate = self
        return frc
    }()
    
    func updatePerson(_ person: Person) {
        person.name = "Updated Name"
        do {
            try context.save()
        } catch {
            print("Save error: \(error)")
        }
    }
}
Note: When updating managed objects, always ensure that the context is saved to persist changes to disk.

9. Deleting Managed Objects

Method Description Notes
delete(_ object: NSManagedObject) Deletes a managed object from its managed object context. Object must be inserted into the context before deletion.
deleteObjects(_ objects: [NSManagedObject]) Deletes multiple managed objects from the context. Can be more efficient than deleting one by one.

Example: Deleting a Single Object

let managedObjectContext = persistentContainer.viewContext
let person = NSEntityDescription.entity(forName: "Person", in: managedObjectContext)!
let personObject = NSManagedObject(entity: person, insertInto: managedObjectContext)

// Delete the object
managedObjectContext.delete(personObject)

Example: Deleting Multiple Objects

let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
let persons = try! managedObjectContext.fetch(fetchRequest)

managedObjectContext.deleteObjects(persons)
Note: Changes are not saved to the persistent store until you call save() on the managed object context.

10. Saving Context Changes

Method Description Notes
save() Saves changes to the persistent store Throws an error if save fails
hasChanges Indicates if there are unsaved changes Read-only property

Example: Saving Context Changes

let context = persistentContainer.viewContext
context.insertedObjects // Check for new objects
context.updatedObjects // Check for updated objects
context.deletedObjects // Check for deleted objects

do {
    try context.save()
    print("Changes saved successfully")
} catch {
    print("Failed to save context: \(error)")
}
Note: Always handle the save() error to prevent data loss.

11. Using Batch Operations

Batch operations in Core Data allow you to perform multiple changes efficiently without individual fetch or save operations. These are particularly useful for bulk updates, deletes, or inserts.

Operation Description Usage
NSBatchInsertRequest Used to insert multiple objects into a Core Data store in one operation. let insertRequest = NSBatchInsertRequest(entity: entity, values: [ ... ])
NSBatchUpdateRequest Updates all matching objects in the store with new values. let updateRequest = NSBatchUpdateRequest(entity: entity, propertiesToUpdate: [ ... ])
NSBatchDeleteRequest Deletes all matching objects from the store. let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)

Example: Batch Insert

let context = persistentContainer.newBackgroundContext()
let entity = NSEntityDescription.entity(forName: "Person", in: context)
let values = [
    ["name": "Alice", "age": 30],
    ["name": "Bob", "age": 25]
]

let insertRequest = NSBatchInsertRequest(entity: entity!, values: values)
try context.execute(insertRequest)

Example: Batch Update

let context = persistentContainer.newBackgroundContext()
let entity = NSEntityDescription.entity(forName: "Person", in: context)
let updateRequest = NSBatchUpdateRequest(entity: entity!, propertiesToUpdate: ["age": 35])
try context.execute(updateRequest)

Example: Batch Delete

let context = persistentContainer.newBackgroundContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "age < 20")

let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
try context.execute(deleteRequest)
Note: Batch operations do not trigger KVO notifications or faulting, so they are faster but less flexible than individual object manipulation.

12. Implementing Background Contexts

Background contexts are used to perform Core Data operations off the main thread, improving app responsiveness. They are typically created with a private queue concurrency type.

Property Description Value
concurrencyType Queue type for the context NSPrivateQueueConcurrencyType
parent Parent context (optional) NSManagedObjectContext

Example: Creating a Background Context

let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
backgroundContext.parent = mainContext

Example: Saving Changes on Background Context

backgroundContext.perform {
    // Perform changes here
    do {
        try backgroundContext.save()
        mainContext.perform {
            try? mainContext.save() // Save parent context on main thread
        }
    } catch {
        print("Error saving background context: \(error)")
    }
}

Was this article helpful?