Chapter 6: Creating and Saving Data Contexts
The Philosophy of Managed Object Contexts
The Context as a Scratchpad
The Main Context: A Double-Edged Sword
- The user taps "Add Transaction."
- You create a new
Transactionobject directly on theviewContext. - The user fills out half the form but then switches to another tab to check a category name.
- In that other tab, a background network sync completes, and its changes are merged and saved into the
viewContext.
The Solution: Child Contexts
- If the user taps "Cancel", we simply let the View and ViewModel deallocate, which destroys
ChildContext1and the draft transaction. TheMainContextis completely untouched. - If the user taps "Save", we save
ChildContext1, pushing the completed, validated transaction into theMainContext. We can then choose when to save theMainContextto disk.
Memory Management Implications
Architectural Setup: The Core Data Provider
import Foundation
import CoreData
final class CoreDataProvider {
static let shared = CoreDataProvider()
let persistentContainer: NSPersistentContainer
var viewContext: NSManagedObjectContext {
persistentContainer.viewContext
}
private init() {
persistentContainer = NSPersistentContainer(name: "ExpenseTrackerModel")
// Optional: Advanced Performance Tuning
// Enable Write-Ahead Logging (WAL) for SQLite.
// WAL mode improves concurrency by allowing readers to read while writers write.
if let storeDescription = persistentContainer.persistentStoreDescriptions.first {
storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
persistentContainer.loadPersistentStores { description, error in
if let error = error {
// In production, avoid fatalError. Implement a fallback or UI alert.
// For this architecture chapter, we halt if the DB cannot load.
fatalError("Failed to load Core Data stack: \(error.localizedDescription)")
}
}
// CRITICAL: Automatically merge changes saved in other contexts (like background contexts)
// or pushed up from child contexts, keeping the UI up to date.
viewContext.automaticallyMergesChangesFromParent = true
// Establish a strict merge policy to resolve conflicts gracefully.
// If a child context pushes a change that conflicts with the parent, the in-memory (object) changes win.
viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
/// Vends a new ephemeral child context for data entry and editing.
///
/// - Returns: A main-queue bound context whose parent is the viewContext.
func newChildContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = viewContext
// The child also needs a merge policy. If the parent changed while the child was editing,
// we usually want the child's (user's recent) changes to overwrite the parent.
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
}
[!TIP] Merge Policies: Conflicts happen. What if you open a child context to edit a Category, but a background sync deletes that Category before you tap save? Without a
mergePolicy, Core Data will throw a fatal constraint error on save.NSMergeByPropertyObjectTrumpMergePolicytells Core Data: "Take my new changes in memory, and overwrite whatever conflicts you find."
Creating Categories: The Lifecycle of a Draft
The Category Entity Structure
id(UUID, non-optional)name(String, non-optional)colorHex(String, non-optional, default "#FFFFFF")
The Repository Abstraction
import Foundation
import CoreData
final class CoreDataProvider {
static let shared = CoreDataProvider()
let persistentContainer: NSPersistentContainer
var viewContext: NSManagedObjectContext { persistentContainer.viewContext }
private init() {
persistentContainer = NSPersistentContainer(name: "ExpenseTrackerModel")
if let storeDescription = persistentContainer.persistentStoreDescriptions.first {
storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
persistentContainer.loadPersistentStores { description, error in
if let error = error { fatalError("Failed to load Core Data stack: \(error.localizedDescription)") }
}
viewContext.automaticallyMergesChangesFromParent = true
viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
func newChildContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
}
protocol CategoryRepositoryProtocol {
func createChildContext() -> NSManagedObjectContext
func save(context: NSManagedObjectContext) throws
}
final class CategoryRepository: CategoryRepositoryProtocol {
private let coreDataProvider: CoreDataProvider
init(provider: CoreDataProvider = .shared) {
self.coreDataProvider = provider
}
func createChildContext() -> NSManagedObjectContext {
return coreDataProvider.newChildContext()
}
func save(context: NSManagedObjectContext) throws {
// 1. Save the child context to push the draft to the parent (viewContext)
guard context.hasChanges else { return }
try context.save()
// 2. Save the main context to persist the pushed changes to the SQLite disk
let mainContext = coreDataProvider.viewContext
if mainContext.hasChanges {
try mainContext.save()
}
}
}
The Two-Step Save Anatomy
The ViewModel
import Foundation
import CoreData
import Observation
// Mock Category for compilation
@objc(Category)
public class Category: NSManagedObject {
@NSManaged public var id: UUID?
@NSManaged public var name: String?
@NSManaged public var colorHex: String?
}
final class CoreDataProvider {
static let shared = CoreDataProvider()
let persistentContainer: NSPersistentContainer
var viewContext: NSManagedObjectContext { persistentContainer.viewContext }
private init() {
persistentContainer = NSPersistentContainer(name: "ExpenseTrackerModel")
if let storeDescription = persistentContainer.persistentStoreDescriptions.first {
storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
persistentContainer.loadPersistentStores { description, error in
if let error = error { fatalError("Failed to load Core Data stack: \(error.localizedDescription)") }
}
viewContext.automaticallyMergesChangesFromParent = true
viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
func newChildContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
}
protocol CategoryRepositoryProtocol {
func createChildContext() -> NSManagedObjectContext
func save(context: NSManagedObjectContext) throws
}
final class CategoryRepository: CategoryRepositoryProtocol {
private let coreDataProvider: CoreDataProvider
init(provider: CoreDataProvider = .shared) {
self.coreDataProvider = provider
}
func createChildContext() -> NSManagedObjectContext {
return coreDataProvider.newChildContext()
}
func save(context: NSManagedObjectContext) throws {
guard context.hasChanges else { return }
try context.save()
let mainContext = coreDataProvider.viewContext
if mainContext.hasChanges { try mainContext.save() }
}
}
@Observable final class AddCategoryViewModel {
var name: String = ""
var colorHex: String = "#FF0000"
var errorMessage: String?
private let repository: CategoryRepositoryProtocol
// The ephemeral scratchpad for this specific screen
private let childContext: NSManagedObjectContext
// The actual managed object we are building
private var draftCategory: Category
init(repository: CategoryRepositoryProtocol = CategoryRepository()) {
self.repository = repository
// 1. Provision a fresh scratchpad
self.childContext = repository.createChildContext()
// 2. Create the entity explicitly ON THE SCRATCHPAD.
// It does not exist in the main context yet.
self.draftCategory = Category(context: self.childContext)
self.draftCategory.id = UUID()
}
func save() -> Bool {
// Pre-save Validation
guard !name.trimmingCharacters(in: .whitespaces).isEmpty else {
errorMessage = "Category name cannot be empty."
return false
}
// Hydrate the Core Data object with UI state
draftCategory.name = name
draftCategory.colorHex = colorHex
do {
// Trigger the two-step save via the repository
try repository.save(context: childContext)
return true
} catch {
handleCoreDataError(error)
return false
}
}
private func handleCoreDataError(_ error: Error) {
// We will expand on mapping this to human-readable strings later in the chapter.
let nsError = error as NSError
errorMessage = "Failed to save category: \(nsError.localizedDescription)"
print("Unresolved Core Data error \(nsError), \(nsError.userInfo)")
}
}
The SwiftUI View Integration
import SwiftUI
import Foundation
import CoreData
import Observation
// Mock Category for compilation
@objc(Category)
public class Category: NSManagedObject {
@NSManaged public var id: UUID?
@NSManaged public var name: String?
@NSManaged public var colorHex: String?
}
final class CoreDataProvider {
static let shared = CoreDataProvider()
let persistentContainer: NSPersistentContainer
var viewContext: NSManagedObjectContext { persistentContainer.viewContext }
private init() {
persistentContainer = NSPersistentContainer(name: "ExpenseTrackerModel")
if let storeDescription = persistentContainer.persistentStoreDescriptions.first {
storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
persistentContainer.loadPersistentStores { description, error in
if let error = error { fatalError("Failed to load Core Data stack: \(error.localizedDescription)") }
}
viewContext.automaticallyMergesChangesFromParent = true
viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
func newChildContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
}
protocol CategoryRepositoryProtocol {
func createChildContext() -> NSManagedObjectContext
func save(context: NSManagedObjectContext) throws
}
final class CategoryRepository: CategoryRepositoryProtocol {
private let coreDataProvider: CoreDataProvider
init(provider: CoreDataProvider = .shared) {
self.coreDataProvider = provider
}
func createChildContext() -> NSManagedObjectContext {
return coreDataProvider.newChildContext()
}
func save(context: NSManagedObjectContext) throws {
guard context.hasChanges else { return }
try context.save()
let mainContext = coreDataProvider.viewContext
if mainContext.hasChanges { try mainContext.save() }
}
}
@Observable final class AddCategoryViewModel {
var name: String = ""
var colorHex: String = "#FF0000"
var errorMessage: String?
private let repository: CategoryRepositoryProtocol
private let childContext: NSManagedObjectContext
private var draftCategory: Category
init(repository: CategoryRepositoryProtocol = CategoryRepository()) {
self.repository = repository
self.childContext = repository.createChildContext()
self.draftCategory = Category(context: self.childContext)
self.draftCategory.id = UUID()
}
func save() -> Bool {
guard !name.trimmingCharacters(in: .whitespaces).isEmpty else {
errorMessage = "Category name cannot be empty."
return false
}
draftCategory.name = name
draftCategory.colorHex = colorHex
do {
try repository.save(context: childContext)
return true
} catch {
handleCoreDataError(error)
return false
}
}
private func handleCoreDataError(_ error: Error) {
let nsError = error as NSError
errorMessage = "Failed to save category: \(nsError.localizedDescription)"
print("Unresolved Core Data error \(nsError), \(nsError.userInfo)")
}
}
struct AddCategoryView: View {
@Environment(\.dismiss) private var dismiss
@State private var viewModel = AddCategoryViewModel()
var body: some View {
NavigationView {
Form {
Section(header: Text("Category Details")) {
TextField("Name", text: $viewModel.name)
// In a real app, use a ColorPicker, but we use a TextField for simplicity
TextField("Color Hex (e.g. #FF5733)", text: $viewModel.colorHex)
}
if let errorMessage = viewModel.errorMessage {
Section {
Text(errorMessage)
.foregroundColor(.red)
.font(.callout)
}
}
}
.navigationTitle("New Category")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
// MAGICAL DISCARD:
// The user cancelled. We do nothing but dismiss.
// The AddCategoryViewModel is deallocated.
// Its childContext is deallocated.
// The draftCategory is wiped from memory.
// The main database is untouched and pristine.
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
if viewModel.save() {
dismiss()
}
}
}
}
}
}
}
Adding Transactions: The Context Boundary Challenge
The Transaction Entity Structure
id(UUID)amount(Double)date(Date)note(String, optional)category(Relationship toCategory, To-One, Non-optional)
The Repository for Transactions
import Foundation
import CoreData
// Mock Category and Transaction for compilation
@objc(Category)
public class Category: NSManagedObject {
@NSManaged public var id: UUID?
@NSManaged public var name: String?
@NSManaged public var colorHex: String?
}
@objc(Transaction)
public class Transaction: NSManagedObject {
@NSManaged public var id: UUID?
@NSManaged public var amount: Double
@NSManaged public var date: Date?
@NSManaged public var note: String?
@NSManaged public var category: Category?
@NSManaged public var isCleared: Bool
}
final class CoreDataProvider {
static let shared = CoreDataProvider()
let persistentContainer: NSPersistentContainer
var viewContext: NSManagedObjectContext { persistentContainer.viewContext }
private init() {
persistentContainer = NSPersistentContainer(name: "ExpenseTrackerModel")
if let storeDescription = persistentContainer.persistentStoreDescriptions.first {
storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
persistentContainer.loadPersistentStores { description, error in
if let error = error { fatalError("Failed to load Core Data stack: \(error.localizedDescription)") }
}
viewContext.automaticallyMergesChangesFromParent = true
viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
func newChildContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
}
protocol TransactionRepositoryProtocol {
func createChildContext() -> NSManagedObjectContext
func save(context: NSManagedObjectContext) throws
func fetchCategories() -> [Category]
}
final class TransactionRepository: TransactionRepositoryProtocol {
private let coreDataProvider: CoreDataProvider
init(provider: CoreDataProvider = .shared) {
self.coreDataProvider = provider
}
func createChildContext() -> NSManagedObjectContext {
return coreDataProvider.newChildContext()
}
func save(context: NSManagedObjectContext) throws {
// Standard two-step save implementation
guard context.hasChanges else { return }
try context.save()
let main = coreDataProvider.viewContext
if main.hasChanges { try main.save() }
}
func fetchCategories() -> [Category] {
let request: NSFetchRequest = Category.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \Category.name, ascending: true)]
do {
// We fetch on the main context because this data is purely for READ-ONLY UI display.
return try coreDataProvider.viewContext.fetch(request)
} catch {
print("Failed to fetch categories: \(error)")
return []
}
}
}
The Transaction ViewModel and The Boundary Crash
The Fix: Crossing the Boundary Safely
import Foundation
import CoreData
import Observation
// Mock Category and Transaction for compilation
@objc(Category)
public class Category: NSManagedObject {
@NSManaged public var id: UUID?
@NSManaged public var name: String?
@NSManaged public var colorHex: String?
}
@objc(Transaction)
public class Transaction: NSManagedObject {
@NSManaged public var id: UUID?
@NSManaged public var amount: Double
@NSManaged public var date: Date?
@NSManaged public var note: String?
@NSManaged public var category: Category?
@NSManaged public var isCleared: Bool
}
final class CoreDataProvider {
static let shared = CoreDataProvider()
let persistentContainer: NSPersistentContainer
var viewContext: NSManagedObjectContext { persistentContainer.viewContext }
private init() {
persistentContainer = NSPersistentContainer(name: "ExpenseTrackerModel")
if let storeDescription = persistentContainer.persistentStoreDescriptions.first {
storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
persistentContainer.loadPersistentStores { description, error in
if let error = error { fatalError("Failed to load Core Data stack: \(error.localizedDescription)") }
}
viewContext.automaticallyMergesChangesFromParent = true
viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
func newChildContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
}
protocol TransactionRepositoryProtocol {
func createChildContext() -> NSManagedObjectContext
func save(context: NSManagedObjectContext) throws
func fetchCategories() -> [Category]
}
final class TransactionRepository: TransactionRepositoryProtocol {
private let coreDataProvider: CoreDataProvider
init(provider: CoreDataProvider = .shared) {
self.coreDataProvider = provider
}
func createChildContext() -> NSManagedObjectContext {
return coreDataProvider.newChildContext()
}
func save(context: NSManagedObjectContext) throws {
// Standard two-step save implementation
guard context.hasChanges else { return }
try context.save()
let main = coreDataProvider.viewContext
if main.hasChanges { try main.save() }
}
func fetchCategories() -> [Category] {
let request: NSFetchRequest = Category.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \Category.name, ascending: true)]
do {
// We fetch on the main context because this data is purely for READ-ONLY UI display.
return try coreDataProvider.viewContext.fetch(request)
} catch {
print("Failed to fetch categories: \(error)")
return []
}
}
}
@Observable final class AddTransactionViewModel {
var amountString: String = ""
var note: String = ""
var date: Date = Date()
// The category selected by the user (originates from main context)
var selectedCategory: Category?
var availableCategories: [Category] = []
var errorMessage: String?
private let repository: TransactionRepositoryProtocol
private let childContext: NSManagedObjectContext
private var draftTransaction: Transaction
init(repository: TransactionRepositoryProtocol = TransactionRepository()) {
self.repository = repository
self.childContext = repository.createChildContext()
self.draftTransaction = Transaction(context: childContext)
self.draftTransaction.id = UUID()
// Populate the dropdown data
self.availableCategories = repository.fetchCategories()
}
func save() -> Bool {
guard let amount = Double(amountString), amount > 0 else {
errorMessage = "Please enter a valid amount greater than 0."
return false
}
guard let categoryFromMainContext = selectedCategory else {
errorMessage = "Please select a category."
return false
}
draftTransaction.amount = amount
draftTransaction.note = note
draftTransaction.date = date
// CRITICAL BOUNDARY CROSSING:
// Bring the selected category into the child context!
do {
// existingObject(with:) checks if the object still exists in the store.
// If another thread deleted it, this will throw an error rather than crash.
let categoryInChildContext = try childContext.existingObject(with: categoryFromMainContext.objectID) as! Category
// Now both objects live in the child context. It is safe to link them.
draftTransaction.category = categoryInChildContext
} catch {
errorMessage = "The selected category is no longer available."
return false
}
do {
try repository.save(context: childContext)
return true
} catch {
// Error handling will be expanded next
errorMessage = "Save failed: \(error.localizedDescription)"
return false
}
}
}
[!CAUTION]
object(with:)vsexistingObject(with:)Core Data offersobject(with: objectID)andexistingObject(with: objectID). Always preferexistingObject(with:)for crossing contexts.object(with:)always returns an object (often an empty fault), even if the object has been deleted from the database by another thread! If you useobject(with:)and save, you might accidentally recreate a ghost object or crash during the save phase.existingObjectperforms an actual check and throws safely if the item is gone.
Advanced Error Handling and Validation
- Maximum/Minimum values for Integers/Doubles.
- Maximum length or Regex pattern matching for Strings.
- Non-optional requirements.
Decoding Core Data Validation Errors
import Foundation
import CoreData
func humanReadableCoreDataError(for error: Error) -> String {
let nsError = error as NSError
// Handle Core Data constraint/validation errors
if nsError.domain == NSCocoaErrorDomain {
// Handle multiple simultaneous errors
if nsError.code == NSValidationMultipleErrorsError {
if let multipleErrors = nsError.userInfo[NSDetailedErrorsKey] as? [NSError] {
// Map the array of errors to strings and join them
let messages = multipleErrors.map { humanReadableCoreDataError(for: $0) }
return messages.joined(separator: "\n")
}
return "Multiple validation errors occurred. Please check your inputs."
}
// Handle individual errors
switch nsError.code {
case NSValidationMissingMandatoryPropertyError:
// You can extract the offending property name!
let property = nsError.userInfo[NSValidationKeyErrorKey] as? String ?? "A field"
return "\(property.capitalized) is required."
case NSValidationNumberTooLargeError:
let property = nsError.userInfo[NSValidationKeyErrorKey] as? String ?? "A number"
return "\(property.capitalized) is too large."
case NSValidationNumberTooSmallError:
return "A number entered is too small."
case NSValidationStringTooLongError:
return "Text entered exceeds the maximum length."
case NSValidationStringTooShortError, NSValidationStringPatternMatchingError:
return "Text entered is invalid or too short."
default:
return "A database validation error occurred (Code: \(nsError.code))."
}
}
// Handle SQLite store level errors (e.g., Unique Constraints)
if nsError.domain == NSCoreDataErrorDomain {
if nsError.code == NSConstraintConflictError {
return "An item with this information already exists."
}
}
return nsError.localizedDescription
}
Performance and Memory Implications of Saving
[!WARNING] Saving is an I/O Operation Even on modern iPhones with NVMe SSDs, writing to disk takes time. When you call
try viewContext.save(), Core Data orchestrates a lock on the persistent store, generates SQLINSERT/UPDATEstatements, and writes them to the SQLite file. If you do this on the main thread frequently, your UI will drop frames, and animations will stutter.
The Anti-Pattern: Over-Saving
import Foundation
import CoreData
@objc(Transaction)
public class Transaction: NSManagedObject {
@NSManaged public var id: UUID?
@NSManaged public var amount: Double
@NSManaged public var date: Date?
@NSManaged public var note: String?
@NSManaged public var isCleared: Bool
}
class TransactionHelper {
let coreDataProvider = CoreDataProvider.shared
// BAD PRACTICE
func toggleTransactionClearedStatus(transaction: Transaction) {
transaction.isCleared.toggle()
try? coreDataProvider.viewContext.save() // Triggers immediate disk I/O on the main thread!
}
}
The Solution: Coalescing and Lifecycle Saves
import Foundation
import CoreData
class TransactionHelperGood {
// GOOD PRACTICE
func toggleTransactionClearedStatus(transaction: Transaction) {
// Modify the object in-memory.
transaction.isCleared.toggle()
// Notice we do NOT save here.
// The SwiftUI UI updates instantly because it observes the viewContext via @FetchRequest
// or NSFetchedResultsController. The app feels infinitely fast.
}
}
- When pushing from a Child Context: As we saw in our forms, we must save the main context immediately after the child context saves to ensure the new distinct entity makes it to disk safely.
- App Lifecycle Events: Defer saving transient state (like toggles, reordering, expanded/collapsed states) until the user leaves the app.
import SwiftUI
import CoreData
@main
struct ExpenseTrackerApp: App {
@Environment(\.scenePhase) var scenePhase
let coreDataProvider = CoreDataProvider.shared
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { oldPhase, newPhase in
if newPhase == .background || newPhase == .inactive {
let context = coreDataProvider.viewContext
// Check hasChanges first to avoid unnecessary disk hits
if context.hasChanges {
do {
try context.save()
} catch {
// Log error to crash reporting service
print("Failed to perform lifecycle save: \(error)")
}
}
}
}
}
}
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
Summary
- Child Contexts as Scratchpads: Utilizing ephemeral contexts for data entry allows for safe, transactional edits that can be discarded instantly without polluting the main database.
- The Two-Step Save: Understanding that saving a child context only pushes data to memory, and requiring a subsequent save on the main context to trigger disk I/O.
- Crossing Context Boundaries: Safely resolving the infamous cross-context relationship crash by fetching objects into the local scratchpad using
existingObject(with: objectID). - Intelligent Error Handling: Interrogating the
NSCocoaErrorDomainandNSDetailedErrorsKeyto transform cryptic database validation failures into actionable UI alerts. - Performance Tuning: Embracing save coalescing and lifecycle hooks to minimize main thread disk I/O, ensuring your app remains 60fps-smooth even under heavy data manipulation.