Chapter 14: Final Polish: The Complete Expense Tracker App

1. The Architectural Blueprint

  1. Testability: By hiding Core Data behind a Repository protocol, we can inject a mock repository into our ViewModels for unit testing and SwiftUI Previews. You cannot easily mock @Environment(\.managedObjectContext).
  2. Separation of Concerns: Views should only format and display data. They should not contain database query logic (predicates, sort descriptors).
  3. Scalability: When your app grows, you might want to switch your local cache from Core Data to SwiftData, Realm, or a simple file-based system. With a Repository pattern, your UI layer remains completely untouched.
  4. Performance Control: @FetchRequest binds the lifecycle of a database query to the lifecycle of a View. If the view redraws frequently, it can cause unintended performance overhead. By controlling the fetch lifecycle in the ViewModel, we dictate exactly when and how data is queried.
graph TD subgraph UI_Layer___SwiftUI_Views__No_Core_Data_imports___ ["UI Layer - SwiftUI Views (No Core Data imports)"] A[DashboardView] B[ExpenseListView] C[AddExpenseView] end subgraph Presentation_Layer___ViewModels__State___Business_Logic___ ["Presentation Layer - ViewModels (State & Business Logic)"] D[DashboardViewModel] E[ExpenseListViewModel] F[AddExpenseViewModel] end subgraph Domain___Data_Layer___Repositories__Protocols___Implementations___ ["Domain & Data Layer - Repositories (Protocols & Implementations)"] G[ExpenseRepository] H[CategoryRepository] end subgraph Core_Data_Stack___Infrastructure__ ["Core Data Stack - Infrastructure"] I[NSPersistentContainer] J[NSFetchedResultsController Wrapper] K[View Context - Main Thread] L[Background Context - Private Queue] end %% UI to ViewModel bindings A -->|"Observes @Observable"| D B -->|"Observes @Observable"| E C -->|"Binds to / Calls"| F %% ViewModel to Repository calls D -->|"Calculates aggregates via"| G E -->|"Delegates Section Management"| J E -->|"CRUD Operations"| G F -->|"Saves via"| G F -->|"Fetches Categories"| H %% Repository to Core Data interactions G -->|"Executes NSFetchRequest"| K H -->|"Executes NSFetchRequest"| K J -->|"Monitors Context Changes"| K G -.->|"Batch Ops / Heavy Processing"| L %% Internal Core Data synchronization L -.->|"Saves propagate via Notification"| K

The Rules of Engagement

  1. Views Know Nothing: SwiftUI views only interact with ViewModels. They observe @Observable properties and call methods on the ViewModel. They never import CoreData.
  2. ViewModels Orchestrate: ViewModels transform domain data (like NSManagedObject subclasses) into presentation data. They hold references to Repositories or our custom NSFetchedResultsController wrappers.
  3. Repositories Abstract: Repositories handle the actual fetching, saving, deleting, and instantiating of data.
  4. The Stack Endures: The CoreDataManager singleton encapsulates the NSPersistentContainer and its contexts, ensuring thread safety and proper context merging.

2. Solidifying the Core Data Stack

import CoreData
import Foundation

public final class CoreDataManager {
    /// A shared singleton instance. While Singletons can be an anti-pattern, 
    /// having a single source of truth for the Core Data stack is highly recommended.
    public static let shared = CoreDataManager()
    
    public let persistentContainer: NSPersistentContainer
    
    /// A convenience accessor for the main queue context. 
    /// NEVER use this context on a background thread.
    public var viewContext: NSManagedObjectContext {
        persistentContainer.viewContext
    }
    
    /// Initializes the Core Data stack.
    /// - Parameter inMemory: If true, creates an in-memory store. Crucial for Unit Tests and SwiftUI Previews.
    private init(inMemory: Bool = false) {
        persistentContainer = NSPersistentContainer(name: "ExpenseTrackerModel")
        
        if inMemory {
            let description = NSPersistentStoreDescription()
            // Setting the URL to /dev/null creates a true in-memory SQLite store.
            // This is generally faster and more accurate than NSInMemoryStoreType.
            description.url = URL(fileURLWithPath: "/dev/null")
            persistentContainer.persistentStoreDescriptions = [description]
        }
        
        // Essential configuration for modern Core Data applications
        guard let description = persistentContainer.persistentStoreDescriptions.first else {
            fatalError("Failed to retrieve a persistent store description.")
        }
        
        // Enables Persistent History Tracking. This is mandatory if you plan to share the database 
        // with an App Extension (like a Widget) or sync with CloudKit.
        description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        
        // Tells Core Data to post notifications when the store changes remotely (e.g., from an App Group extension)
        description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        
        // Lightweight migration is enabled by default in modern iOS, but it's good practice to be explicit.
        description.shouldInferMappingModelAutomatically = true
        description.shouldMigrateStoreAutomatically = true
        
        persistentContainer.loadPersistentStores { (storeDescription, error) in
            if let error = error as NSError? {
                // EXTREME CAUTION: In production, log this to a crash reporter (e.g., Crashlytics, Sentry). 
                // Do not use fatalError unless you want your users to experience hard crashes on launch due to a corrupted DB.
                // A robust app might attempt to delete the corrupted store and rebuild it here.
                print("Unresolved error \(error), \(error.userInfo)")
            }
        }
        
        // Automatically merges changes saved to its parent store (or background contexts) into the viewContext.
        // MEMORY IMPLICATION: This keeps the viewContext fresh, but can lead to memory bloat if thousands 
        // of objects are created in the background. If you do massive batch imports, consider batching the saves.
        persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
        
        // Merge Policies: NSMergeByPropertyObjectTrumpMergePolicy ensures that if the in-memory object 
        // on the context has been modified, its changes win over what's currently on disk.
        persistentContainer.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    }
    
    /// Vends a new background context for heavy data processing.
    public func newBackgroundContext() -> NSManagedObjectContext {
        let context = persistentContainer.newBackgroundContext()
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        // Give background contexts a name for easier debugging in Instruments.
        context.name = "Background_Importer_Context"
        return context
    }
    
    /// Safely saves the view context if there are changes.
    public func saveContext() {
        let context = persistentContainer.viewContext
        // ALWAYS check hasChanges before saving to avoid unnecessary disk I/O.
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                // Handle save errors gracefully. Present an alert to the user in the UI layer.
                print("Unresolved error saving context: \(nserror), \(nserror.userInfo)")
            }
        }
    }
}

Key Takeaways for the Stack:

  • automaticallyMergesChangesFromParent = true: This is critical for our architecture. When we perform a save operation on a background context (via newBackgroundContext), Core Data will broadcast a notification. Because this flag is true, the viewContext automatically consumes that notification, merges the changes, and triggers our NSFetchedResultsController delegate, which in turn updates our SwiftUI views. All without writing any manual notification observers!
  • /dev/null for In-Memory Stores: Notice we use an SQLite store pointing to /dev/null rather than NSInMemoryStoreType. The legacy NSInMemoryStoreType lacks support for advanced SQLite features (like specific migration edge cases or fetch request behaviors). The /dev/null trick provides an identical SQLite environment that simply evaporates when the app terminates.

3. The NSFetchedResultsController Wrapper

import CoreData
import Combine
import Observation

/// A generic wrapper that adapts NSFetchedResultsController for use with Swift Concurrency and SwiftUI.
@Observable public class FetchControllerWrapper<T: NSManagedObject>: NSObject, NSFetchedResultsControllerDelegate {
    
    public var fetchedObjects: [T] = []
    public var sections: [NSFetchedResultsSectionInfo] = []
    
    public let controller: NSFetchedResultsController<T>
    
    /// Initializes the wrapper with a fetch request.
    /// - Parameters:
    ///   - fetchRequest: The request defining the data to fetch. **Must** include at least one sort descriptor.
    ///   - managedObjectContext: The context to fetch against (usually the main viewContext).
    ///   - sectionNameKeyPath: A key path on the entity to group results into sections.
    ///   - cacheName: A name for the FRC cache. Use cautiously!
    public init(fetchRequest: NSFetchRequest<T>,
                managedObjectContext: NSManagedObjectContext,
                sectionNameKeyPath: String? = nil,
                cacheName: String? = nil) {
        
        // Edge Case Protection: FRC requires sort descriptors.
        if fetchRequest.sortDescriptors == nil || fetchRequest.sortDescriptors?.isEmpty == true {
            assertionFailure("FetchControllerWrapper requires a fetch request with at least one sort descriptor.")
        }
        
        self.controller = NSFetchedResultsController(
            fetchRequest: fetchRequest,
            managedObjectContext: managedObjectContext,
            sectionNameKeyPath: sectionNameKeyPath,
            cacheName: cacheName
        )
        super.init()
        self.controller.delegate = self
    }
    
    /// Executes the initial fetch. Must be called explicitly after initialization.
    public func performFetch() {
        do {
            try controller.performFetch()
            updateState()
        } catch {
            print("Failed to perform fetch in wrapper: \(error)")
        }
    }
    
    /// Syncs the FRC's internal state to our @Observable properties.
    private func updateState() {
        // We reassign the array. Because the objects are NSManagedObjects (reference types), 
        // and because SwiftUI's ForEach uses the object's `id` (or `objectID`), SwiftUI is incredibly 
        // smart about computing the diff and only animating the changed rows, even if we reassign the whole array.
        fetchedObjects = controller.fetchedObjects ?? []
        sections = controller.sections ?? []
    }
    
    // MARK: - NSFetchedResultsControllerDelegate
    
    /// Called when the context has processed changes that affect the fetched results.
    public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        // PERFORMANCE TIP: If you have a very active database (e.g., syncing hundreds of items in the background),
        // `controllerDidChangeContent` can fire rapidly. In a highly stressed app, you might want to debounce 
        // this call before updating the @Observable properties to prevent frame drops in SwiftUI.
        updateState()
    }
}

Deep Dive: FRC Caching and Memory

  • cacheName pitfalls: Providing a cacheName tells the FRC to pre-compute the sections and sort order and write them to disk. This is a massive performance boost for complex, sectioned lists. However, if you ever change the NSPredicate or NSSortDescriptor dynamically, you must call NSFetchedResultsController.deleteCache(withName:) before performing the new fetch, or your app will crash or display corrupt data.
  • Faulting: By default, NSFetchRequest returns "faults" (hollow shells of objects). fetchedObjects might contain 10,000 items, but it uses almost no RAM until SwiftUI actually scrolls a specific row into view and accesses the expense.amount property, which "fires the fault" and loads the data from SQLite.

4. The Repositories: Abstracting Core Data

classDiagram class ExpenseRepositoryProtocol { <> +addExpense(amount: Double, date: Date, note: String, category: CategoryEntity) +deleteExpense(_ expense: ExpenseEntity) +getTotalExpenses(for month: Date) Double +getExpensesGroupedByCategory(for month: Date) Array } class ExpenseRepository { -context: NSManagedObjectContext } class MockExpenseRepository { -inMemoryStore: [ExpenseEntity] } ExpenseRepositoryProtocol <|-- ExpenseRepository ExpenseRepositoryProtocol <|-- MockExpenseRepository

The Expense Repository

import Foundation
import CoreData

public protocol ExpenseRepositoryProtocol {
    func addExpense(amount: Double, date: Date, note: String, category: CategoryEntity)
    func deleteExpense(_ expense: ExpenseEntity)
    func getTotalExpenses(for month: Date) -> Double
    func getExpensesGroupedByCategory(for month: Date) -> [(category: String, amount: Double)]
}

public class ExpenseRepository: ExpenseRepositoryProtocol {
    private let context: NSManagedObjectContext
    
    // Dependency injection allows us to pass a specific context (like a background context) if needed.
    public init(context: NSManagedObjectContext = CoreDataManager.shared.viewContext) {
        self.context = context
    }
    
    public func addExpense(amount: Double, date: Date, note: String, category: CategoryEntity) {
        // ALWAYS perform operations on the context's queue to avoid thread-safety crashes.
        // Since we usually inject the viewContext, performAndWait ensures it runs on the main thread safely.
        context.performAndWait {
            let expense = ExpenseEntity(context: context)
            expense.id = UUID()
            expense.amount = amount
            expense.date = date
            expense.note = note
            expense.category = category
            
            saveContext()
        }
    }
    
    public func deleteExpense(_ expense: ExpenseEntity) {
        context.performAndWait {
            context.delete(expense)
            saveContext()
        }
    }
    
    /// Calculates the sum of all expenses for a given month using an SQLite-level SUM operation.
    public func getTotalExpenses(for month: Date) -> Double {
        var total: Double = 0.0
        
        // Thread safety is paramount. Contexts are bound to specific queues.
        context.performAndWait {
            let calendar = Calendar.current
            guard let startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: month)),
                  let endOfMonth = calendar.date(byAdding: DateComponents(month: 1, day: -1), to: startOfMonth) else {
                return
            }
            
            // 1. We request a Dictionary instead of Managed Objects. This is extremely lightweight.
            let request: NSFetchRequest<NSDictionaryResultType> = NSFetchRequest(entityName: "ExpenseEntity")
            request.resultType = .dictionaryResultType
            
            // 2. Filter to the specific month
            request.predicate = NSPredicate(format: "date >= %@ AND date <= %@", startOfMonth as NSDate, endOfMonth as NSDate)
            
            // 3. Create the SQLite SUM function definition
            let sumExpressionDesc = NSExpressionDescription()
            sumExpressionDesc.name = "totalSum"
            // The function "sum:" applies to the keyPath "amount"
            sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "amount")])
            sumExpressionDesc.expressionResultType = .doubleAttributeType
            
            request.propertiesToFetch = [sumExpressionDesc]
            
            do {
                let results = try context.fetch(request)
                if let resultDict = results.first as? [String: Double], let sum = resultDict["totalSum"] {
                    total = sum
                }
            } catch {
                print("Error calculating total expenses: \(error)")
            }
        }
        
        return total
    }
    
    /// Groups expenses by category and sums them up using an SQLite-level GROUP BY.
    public func getExpensesGroupedByCategory(for month: Date) -> [(category: String, amount: Double)] {
        var groupedResults: [(category: String, amount: Double)] = []
        
        context.performAndWait {
            let calendar = Calendar.current
            guard let startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: month)),
                  let endOfMonth = calendar.date(byAdding: DateComponents(month: 1, day: -1), to: startOfMonth) else {
                return
            }
            
            let request: NSFetchRequest<NSDictionaryResultType> = NSFetchRequest(entityName: "ExpenseEntity")
            request.resultType = .dictionaryResultType
            request.predicate = NSPredicate(format: "date >= %@ AND date <= %@", startOfMonth as NSDate, endOfMonth as NSDate)
            
            let sumExpressionDesc = NSExpressionDescription()
            sumExpressionDesc.name = "categoryTotal"
            sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "amount")])
            sumExpressionDesc.expressionResultType = .doubleAttributeType
            
            // 4. GROUP BY setup. We tell SQLite to fetch the category name and the sum description, and group by the name.
            request.propertiesToFetch = ["category.name", sumExpressionDesc]
            request.propertiesToGroupBy = ["category.name"]
            
            do {
                let results = try context.fetch(request)
                groupedResults = results.compactMap { dict in
                    guard let categoryName = dict["category.name"] as? String,
                          let total = dict["categoryTotal"] as? Double else { return nil }
                    return (category: categoryName, amount: total)
                }
            } catch {
                print("Error grouping expenses: \(error)")
            }
        }
        return groupedResults
    }
    
    private func saveContext() {
        if context.hasChanges {
            do { try context.save() } catch { print("Save error: \(error)") }
        }
    }
}

[!TIP] Database-Level Optimization vs Application-Level Math
If we had used standard SwiftUI patterns, we would fetch a [ExpenseEntity] array into memory, and then use Swift's reduce to sum them up. For 10,000 transactions, the NSExpressionDescription method takes ~2ms and negligible memory because the math happens inside the C/C++ SQLite engine. The array/reduce method would take ~50-100ms and cause massive memory spikes as 10,000 objects are faulted into the context.

5. The ViewModels

Expense List ViewModel

import Foundation
import CoreData
import Observation
import Combine

@Observable public class ExpenseListViewModel {
    public var sections: [NSFetchedResultsSectionInfo] = []
    
    private let repository: ExpenseRepositoryProtocol
    public let fetchControllerWrapper: FetchControllerWrapper<ExpenseEntity>
    private var cancellables = Set<AnyCancellable>()
    
    public init(repository: ExpenseRepositoryProtocol = ExpenseRepository()) {
        self.repository = repository
        
        // 1. Create the Fetch Request
        let request: NSFetchRequest<ExpenseEntity> = ExpenseEntity.fetchRequest()
        
        // Primary sort MUST match the section grouping logically (e.g., date descending).
        let dateSort = NSSortDescriptor(keyPath: \ExpenseEntity.date, ascending: false)
        request.sortDescriptors = [dateSort]
        
        // MEMORY IMPLICATION: fetchBatchSize is Core Data's secret weapon.
        // Even if the DB has 1,000,000 rows, this ensures only 20 objects are fully materialized 
        // into memory at any given time. As SwiftUI scrolls, Core Data transparently faults and un-faults batches.
        request.fetchBatchSize = 20
        
        // 2. Initialize the Wrapper with sectionNameKeyPath
        // We assume we have a transient property `@objc var monthYearString: String` on ExpenseEntity.
        self.fetchControllerWrapper = FetchControllerWrapper(
            fetchRequest: request,
            managedObjectContext: CoreDataManager.shared.viewContext,
            sectionNameKeyPath: "monthYearString" // Crucial for Sectioned Lists
        )
        
        // 3. Bind the wrapper's sections to our own property.
        // We ensure delivery on the main thread since SwiftUI views observe this.
        // (Note: In a pure Observation approach, you might simply reference fetchControllerWrapper.sections directly 
        // in your View, or observe the wrapper directly).
        self.fetchControllerWrapper.performFetch()
    }
    
    public func delete(at indexSet: IndexSet, in section: NSFetchedResultsSectionInfo) {
        guard let objects = section.objects as? [ExpenseEntity] else { return }
        
        for index in indexSet {
            let expense = objects[index]
            repository.deleteExpense(expense)
        }
    }
}

[!IMPORTANT] Transient Properties for Sections: To group an NSFetchedResultsController by month and year, create an extension on your ExpenseEntity with an @objc computed property that returns a formatted string (e.g., "October 2023"). Use this property name as the sectionNameKeyPath. Because it's computed on the fly, it doesn't take up database space, but Core Data can use it to organize the fetched results array into sections!

Dashboard ViewModel

import Foundation
import Combine
import SwiftUI
import CoreData
import Observation

public struct CategoryChartData: Identifiable {
    public let id = UUID()
    public let category: String
    public let amount: Double
}

@Observable public class DashboardViewModel {
    public var totalMonthlyExpense: Double = 0.0
    public var categoryData: [CategoryChartData] = []
    
    private let repository: ExpenseRepositoryProtocol
    private var cancellables = Set<AnyCancellable>()
    
    public init(repository: ExpenseRepositoryProtocol = ExpenseRepository()) {
        self.repository = repository
        
        // Listen for ANY Core Data saves to refresh dashboard metrics automatically.
        // This is a broader net than FRC, but necessary for aggregate queries.
        NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave)
            .receive(on: RunLoop.main)
            .sink { [weak self] notification in
                // Edge Case Optimization: Only refresh if the save came from the viewContext 
                // or if it was merged into it. You can inspect `notification.userInfo` for 
                // inserted/updated/deleted objects to see if an ExpenseEntity actually changed.
                self?.refreshData()
            }
            .store(in: &cancellables)
            
        refreshData()
    }
    
    public func refreshData() {
        let now = Date()
        self.totalMonthlyExpense = repository.getTotalExpenses(for: now)
        
        let rawData = repository.getExpensesGroupedByCategory(for: now)
        // Map domain tuples to View-specific structs suitable for Swift Charts.
        self.categoryData = rawData.map { CategoryChartData(category: $0.category, amount: $0.amount) }
    }
}

6. The Final SwiftUI UI

The Dashboard (Integrating Swift Charts)

import SwiftUI
import Charts

struct DashboardView: View {
    @State private var viewModel = DashboardViewModel()
    @State private var showingAddExpense = false
    
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(spacing: 24) {
                    // Summary Card
                    VStack(spacing: 8) {
                        Text("This Month's Spend")
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                        
                        Text(viewModel.totalMonthlyExpense, format: .currency(code: "USD"))
                            .font(.system(size: 42, weight: .bold, design: .rounded))
                            .foregroundColor(.primary)
                    }
                    .padding()
                    .frame(maxWidth: .infinity)
                    .background(Color(UIColor.secondarySystemBackground))
                    .cornerRadius(16)
                    .padding(.horizontal)
                    
                    // Spending by Category Chart
                    VStack(alignment: .leading) {
                        Text("Top Categories")
                            .font(.headline)
                            .padding(.horizontal)
                        
                        if viewModel.categoryData.isEmpty {
                            Text("No expenses this month.")
                                .foregroundColor(.secondary)
                                .padding()
                        } else {
                            Chart(viewModel.categoryData) { item in
                                BarMark(
                                    x: .value("Amount", item.amount),
                                    y: .value("Category", item.category)
                                )
                                // Dynamically color bars by category name
                                .foregroundStyle(by: .value("Category", item.category))
                                .annotation(position: .trailing) {
                                    Text(item.amount, format: .currency(code: "USD"))
                                        .font(.caption)
                                        .foregroundColor(.secondary)
                                }
                            }
                            .frame(height: 300)
                            .padding()
                        }
                    }
                }
                .padding(.vertical)
            }
            .navigationTitle("Dashboard")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: { showingAddExpense = true }) {
                        Image(systemName: "plus.circle.fill")
                            .font(.title2)
                    }
                }
            }
            .sheet(isPresented: $showingAddExpense) {
                // E.g., AddExpenseView(viewModel: AddExpenseViewModel())
                Text("Add Expense View (Implementation Omitted)")
            }
        }
    }
}

The Sectioned Expense List

import SwiftUI
import CoreData

struct ExpenseListView: View {
    // We use @State to ensure the ViewModel (and its FRC) are only initialized once per view lifecycle.
    @State private var viewModel = ExpenseListViewModel()
    
    var body: some View {
        NavigationStack {
            List {
                // Iterate over FRC sections
                ForEach(viewModel.fetchControllerWrapper.sections, id: \.name) { section in
                    Section(header: Text(section.name)) {
                        
                        // Safely cast the generic objects array to our specific entity
                        if let expenses = section.objects as? [ExpenseEntity] {
                            // By using \.objectID, we give SwiftUI a stable identifier even if data changes
                            ForEach(expenses, id: \.objectID) { expense in
                                ExpenseRow(expense: expense)
                            }
                            .onDelete { indexSet in
                                // Pass the deletion command back to the ViewModel
                                viewModel.delete(at: indexSet, in: section)
                            }
                        }
                    }
                }
            }
            .listStyle(.insetGrouped)
            .navigationTitle("Transactions")
        }
    }
}

struct ExpenseRow: View {
    // EXTREMELY IMPORTANT: By making the property @Bindable, this specific row 
    // view will automatically re-render ONLY if this specific ExpenseEntity changes!
    // This allows us to push edits to a detail screen, pop back, and see the row instantly update.
    @Bindable var expense: ExpenseEntity
    
    var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 4) {
                Text(expense.note ?? "Unknown")
                    .font(.headline)
                Text(expense.category?.name ?? "Uncategorized")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
            
            Spacer()
            
            Text(expense.amount, format: .currency(code: "USD"))
                .fontWeight(.semibold)
                .foregroundColor(expense.amount > 1000 ? .red : .primary)
        }
    }
}

[!NOTE] Why @Bindable on the Row? NSManagedObject conforms to Observable in modern Swift via an extension or native bridging. By wrapping the expense in @Bindable inside the subview, SwiftUI will automatically re-render just that row if a specific attribute changes. If we didn't do this, editing an item deep in the navigation stack might not reflect when you pop back to the list, unless the entire FRC array triggered a redraw.

7. Production Polish: Best Practices

1. Decoupled Architecture for Maximum Testability

2. Predictable UI Performance and Caching

3. Avoiding the Memory Bloat (Faulting & Batching)

4. Safe, Non-Blocking Threading

Conclusion