145 lines
4.6 KiB
Swift
145 lines
4.6 KiB
Swift
import CoreData
|
|
import CloudKit
|
|
|
|
class CoreDataStack: ObservableObject {
|
|
static let shared = CoreDataStack()
|
|
|
|
static let appGroupIdentifier = "group.com.yourteam.investmenttracker"
|
|
static let cloudKitContainerIdentifier = "iCloud.com.yourteam.investmenttracker"
|
|
|
|
lazy var persistentContainer: NSPersistentCloudKitContainer = {
|
|
let container = NSPersistentCloudKitContainer(name: "InvestmentTracker")
|
|
|
|
// App Group store URL for sharing with widgets
|
|
guard let storeURL = FileManager.default
|
|
.containerURL(forSecurityApplicationGroupIdentifier: Self.appGroupIdentifier)?
|
|
.appendingPathComponent("InvestmentTracker.sqlite") else {
|
|
fatalError("Unable to get App Group container URL")
|
|
}
|
|
|
|
let description = NSPersistentStoreDescription(url: storeURL)
|
|
|
|
// CloudKit configuration
|
|
description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
|
|
containerIdentifier: Self.cloudKitContainerIdentifier
|
|
)
|
|
|
|
// History tracking for sync
|
|
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
|
|
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
|
|
|
|
container.persistentStoreDescriptions = [description]
|
|
|
|
container.loadPersistentStores { description, error in
|
|
if let error = error as NSError? {
|
|
// In production, handle this error appropriately
|
|
print("Core Data failed to load: \(error), \(error.userInfo)")
|
|
}
|
|
}
|
|
|
|
// Merge policy - remote changes win
|
|
container.viewContext.automaticallyMergesChangesFromParent = true
|
|
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
|
|
|
// Performance optimizations
|
|
container.viewContext.undoManager = nil
|
|
container.viewContext.shouldDeleteInaccessibleFaults = true
|
|
|
|
// Listen for remote changes
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(processRemoteChanges),
|
|
name: .NSPersistentStoreRemoteChange,
|
|
object: container.persistentStoreCoordinator
|
|
)
|
|
|
|
return container
|
|
}()
|
|
|
|
var viewContext: NSManagedObjectContext {
|
|
return persistentContainer.viewContext
|
|
}
|
|
|
|
private init() {}
|
|
|
|
// MARK: - Save Context
|
|
|
|
func save() {
|
|
let context = viewContext
|
|
guard context.hasChanges else { return }
|
|
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
let nsError = error as NSError
|
|
print("Core Data save error: \(nsError), \(nsError.userInfo)")
|
|
}
|
|
}
|
|
|
|
// MARK: - Background Context
|
|
|
|
func newBackgroundContext() -> NSManagedObjectContext {
|
|
let context = persistentContainer.newBackgroundContext()
|
|
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
|
context.undoManager = nil
|
|
return context
|
|
}
|
|
|
|
func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
|
|
persistentContainer.performBackgroundTask(block)
|
|
}
|
|
|
|
// MARK: - Remote Change Handling
|
|
|
|
@objc private func processRemoteChanges(_ notification: Notification) {
|
|
// Process remote changes on main context
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.objectWillChange.send()
|
|
}
|
|
}
|
|
|
|
// MARK: - Widget Data Refresh
|
|
|
|
func refreshWidgetData() {
|
|
// Trigger widget timeline refresh when data changes
|
|
#if os(iOS)
|
|
if #available(iOS 14.0, *) {
|
|
import WidgetKit
|
|
WidgetCenter.shared.reloadAllTimelines()
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// MARK: - Shared Container for Widgets
|
|
|
|
extension CoreDataStack {
|
|
static var sharedStoreURL: URL? {
|
|
return FileManager.default
|
|
.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)?
|
|
.appendingPathComponent("InvestmentTracker.sqlite")
|
|
}
|
|
|
|
/// Creates a lightweight Core Data stack for widgets (read-only)
|
|
static func createWidgetContainer() -> NSPersistentContainer {
|
|
let container = NSPersistentContainer(name: "InvestmentTracker")
|
|
|
|
guard let storeURL = sharedStoreURL else {
|
|
fatalError("Unable to get shared store URL")
|
|
}
|
|
|
|
let description = NSPersistentStoreDescription(url: storeURL)
|
|
description.isReadOnly = true
|
|
|
|
container.persistentStoreDescriptions = [description]
|
|
|
|
container.loadPersistentStores { _, error in
|
|
if let error = error {
|
|
print("Widget Core Data failed: \(error)")
|
|
}
|
|
}
|
|
|
|
return container
|
|
}
|
|
}
|