Core Data — What It Is, Data Model and How It Works

Author: IT Sectr Published: 2026-05-04 Reading time: 8 min

Core Data is an Apple data management framework providing object-relational mapping for iOS, macOS, tvOS and watchOS. It automates saving, fetching and filtering objects in the application, working on top of SQLite, XML or binary storage. According to Apple Core Data Documentation, the framework uses Managed Object Context and NSPersistentContainer concepts for managing the persistence stack.

Key Takeaways

  • Core Data — Apple framework for object-relational data management in applications.
  • NSManagedObjectModel — data schema description: Entity, Attributes and Relationships.
  • NSManagedObject — object corresponding to one record in the Core Data store.
  • NSManagedObjectContext — workspace for creating, reading and saving objects.
  • NSPersistentContainer — a unified stack combining the model, context and store coordinator.

What Is Core Data and Its Role in iOS

Core Data is an object graph and persistence management framework that is part of Cocoa Touch. Contrary to common misconception, Core Data is not a database, but an object management layer that can use SQLite as one of its stores. The main task of Core Data is to track object changes, manage their lifecycle and synchronize state with disk.

The framework provides an object graph where each Managed Object is tracked by the context for changes. Upon saving, all modified, added and deleted objects are committed to the persistent store in a single transaction. This eliminates the need for the developer to write SQL queries and manage transactions manually.

According to Swift Developer Survey statistics (2025), Core Data is used in 52% of iOS applications working with local data. Despite the emergence of modern alternatives (SwiftData, Realm), Core Data remains the primary framework in existing Apple projects due to its maturity and deep system integration.

Use Core Data for projects with a medium-complexity data model where object relationships, change rollback and automatic caching through the faulting mechanism are needed.

Core Data architecture is built around the Managed Object Context concept — a workspace that tracks all object changes. The context supports undo/redo through the built-in NSUndoManager, allowing you to implement drafts and action cancellation without manually saving state snapshots. When save() is called, the context commits all changes in a single transaction to the persistent store, guaranteeing atomicity and data consistency.

Data Model: Entity, Attributes, Relationships

The Core Data data model is defined in the .xcdatamodeld file — Xcode's visual editor where all Entities, their attributes and relationships are described. At compile time, the model is serialized into .momd and loaded through NSManagedObjectModel.

Entity and Attributes

Entity is a data type description, similar to a table in SQL. Each Entity contains a set of Attributes — named fields with a data type (String, Integer, Date, Boolean, Data). Unlike Room, Core Data requires explicit type selection for each attribute through the model editor.

Relationships

Relationship is a connection between Entities, similar to a foreign key in SQL. Core Data supports all relationship types: one-to-one, one-to-many and many-to-many. Each relationship has a Delete Rule (Cascade, Nullify, Deny) configured — behavior when the related object is deleted.

Delete RuleBehavior on DeletionUse Case Example
CascadeDeletes all related objectsDeleting an order together with its items
NullifySets the inverse relationship to nullDeleting an author without deleting books
DenyBlocks deletion if related objects existProtecting against deleting a category with products

Choosing a Delete Rule is critical for data integrity: Cascade without verification can delete a third of the database, while Deny can block the operation with an unclear error. In production code, Nullify with manual orphan record handling is recommended.

In the Xcode model editor, the developer can define not only Entity and attributes but also constraints (unique constraints), indexes for query acceleration and default values for attributes. All model changes are compiled into a .momd file, which is loaded during NSPersistentContainer initialization. Model Versioning allows maintaining multiple schema versions and performing migration between them.

Core Data Stack: PersistentContainer and Context

NSPersistentContainer is a single object managing the Core Data stack since iOS 10 and macOS 10.12. It encapsulates NSManagedObjectModel, NSPersistentStoreCoordinator and NSManagedObjectContext, automating model loading and store configuration. For older versions, the stack was built manually, but this is no longer recommended.

swift
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { _, error in
    if let error { fatalError("Core Data load failed: \(error)") }
}
let context = container.viewContext

viewContext is the main context tied to the main thread. All reads and UI updates are performed through it. For performance, data writing is recommended to be performed on a background child context with subsequent synchronization.

NSPersistentStoreCoordinator

The NSPersistentStoreCoordinator coordinator connects the model to the physical storage on disk. Core Data supports several store types: SQLite (recommended), Binary and In-Memory. The SQLite store supports migrations, incremental backup and crash resilience during write operations.

NSFetchRequest and Working with Data

NSFetchRequest is an object describing a query to the Core Data store. It contains the Entity name, filter predicate, sort descriptors and fetch settings. The query is executed via context.fetch(), which returns an array of NSManagedObject.

swift
let request = NSFetchRequest<User>(entityName: "User")
request.predicate = NSPredicate(format: "age >= %d", 18)
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
request.fetchLimit = 50

let results = try context.fetch(request)

NSPredicate supports complex conditions: LIKE, IN, BETWEEN, CONTAINS[c] (case-insensitive), SUBQUERY for nested queries on related Entity. Core Data also supports NSFetchedResultsController — a class for reactive data loading in UITableView, which automatically tracks changes and updates the table with animated sections.

Core Data in a Multithreaded Environment

Working with Core Data in a multithreaded application requires strict rule compliance: NSManagedObject cannot be passed directly between threads. Each thread (or queue) must use its own context. The main approach is creating a child NSManagedObjectContext with a private queue (NSPrivateQueueConcurrencyType) for writing and viewContext for reading.

The child context saves to the parent, and then the parent saves to the disk store. This guarantees that changes do not block the main thread and that the UI always sees a consistent state through mergeChanges or automatic viewContext update upon saving.

Core Data uses faulting — a lazy loading mechanism for related objects. When fetching a User without requesting their addresses, the related Address objects are not loaded until they are accessed via dot notation. Faulting saves memory and speeds up loading, but may cause unexpected disk access on the main thread if access is not controlled in background contexts.

For efficient multithreading, use NSBatchInsertRequest and NSBatchDeleteRequest for bulk inserts and deletes without loading objects into memory — this is critical for data synchronization with the server.

Batch operations execute directly at the NSPersistentStoreCoordinator level, bypassing the context and object graph. This allows inserting 10,000 records in milliseconds without creating 10,000 NSManagedObject instances in memory. After executing a batch request, the context must be updated via mergeChangesFromContextDidSaveNotification so that the UI reflects the new data. Apple recommends batch operations for initial data loading and nightly server synchronization.

For change tracking in Core Data, NSPersistentHistoryTracking is used — a mechanism that records every transaction (insert, update, delete) in a separate history. Enabling history tracking allows synchronizing data between different processes and applications working with the same SQLite file, for example between the main application and a Notification Service Extension. Activation is done via NSPersistentStoreDescription with the persistentHistoryTrackingKey flag, and reading via NSPersistentHistoryChangeRequest with filtering by date and transaction type.

For debugging and performance profiling of Core Data, the Core Data Profiler tool from the Instruments suite in Xcode on macOS is used. It shows all fetch, insert, delete and save operations with the duration of each operation and the number of loaded objects in tables and timeline graphs. The developer can identify problem areas: multiple fetches of the same query (lack of caching), fault object leaks during table scrolling or main thread blocking due to synchronous loading of related entities. It is recommended to run profiling on a real device rather than a simulator, as the simulator's performance does not reflect the real behavior of the application on an iPhone or iPad.

Frequently Asked Questions

How is Core Data different from SQLite?

Core Data is not a database, but an object management layer that can use SQLite as a store. Unlike raw SQLite, Core Data tracks object changes, manages rollbacks and provides an object graph with faulting and caching. SQLite gives more control over queries, but requires writing SQL and managing transactions manually.

How to perform Core Data schema migration?

Core Data supports Lightweight Migration for non-destructive changes: adding an attribute, renaming, setting a default value. For complex changes, a Mapping Model is created. Lightweight migration is enabled by the shouldMigrateAutomatically flag in NSPersistentStoreDescription.

Can Core Data be used with SwiftUI?

Yes, Core Data integrates with SwiftUI through the @FetchRequest wrapper for queries and @ObservedObject for change subscription. SwiftUI automatically updates the View when a ManagedObject changes, making Core Data and SwiftUI a compatible stack for state management.

What is a fault in Core Data?

A fault is a lightweight placeholder in the Core Data graph that does not contain the related object's data. When a fault is set (via refreshObject:), the data is unloaded from memory. When a property is accessed, the fault automatically fills with data from the store — this is a lazy loading mechanism that optimizes memory usage.

How to test Core Data code?

For testing, use the In-Memory store type: NSPersistentStoreDescription with NSInMemoryStoreType. The container is created with the model from the test bundle. After each test, delete all objects or recreate the container — this guarantees test case isolation from each other.

Summary

  • Core Data is an object graph management framework that uses SQLite as the default store.
  • NSManagedObjectModel describes the schema: Entity, Attributes, Relationships and Delete Rules.
  • NSPersistentContainer combines the model, coordinator and viewContext into a unified stack.
  • NSFetchRequest with NSPredicate and NSSortDescriptor forms flexible queries to the store.
  • Multithreading requires separate contexts: a child context for writing and viewContext for reading.
  • Faulting defers loading of related objects until first access, saving memory.
  • Lightweight Migration automatically handles non-destructive schema changes without data loss.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also