CoreDataObserver.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import Combine
  2. import CoreData
  3. import Foundation
  4. /// Represents the types of Core Data changes that can be observed
  5. /// Use as an option set to specify which types of changes to monitor
  6. public struct CoreDataChangeTypes: OptionSet {
  7. /// The raw integer value used to store the option set bits
  8. public let rawValue: Int
  9. /// Required initializer for OptionSet conformance
  10. public init(rawValue: Int) {
  11. self.rawValue = rawValue
  12. }
  13. /// Represents newly created/inserted objects in Core Data
  14. /// Binary: 001 (1 << 0)
  15. public static let inserted = CoreDataChangeTypes(rawValue: 1 << 0)
  16. /// Represents modified/updated objects in Core Data
  17. /// Binary: 010 (1 << 1)
  18. public static let updated = CoreDataChangeTypes(rawValue: 1 << 1)
  19. /// Represents removed/deleted objects in Core Data
  20. /// Binary: 100 (1 << 2)
  21. public static let deleted = CoreDataChangeTypes(rawValue: 1 << 2)
  22. /// Convenience option that includes all possible change types
  23. /// This combines inserted, updated, and deleted into a single option
  24. public static let all: CoreDataChangeTypes = [.inserted, .updated, .deleted]
  25. }
  26. /// Creates a publisher that emits sets of NSManagedObjectIDs when Core Data changes occur
  27. /// - Parameter changeTypes: The types of changes to observe (defaults to .all)
  28. /// - Returns: A publisher that emits Sets of NSManagedObjectIDs for the specified change types
  29. func changedObjectsOnManagedObjectContextDidSavePublisher(
  30. observing changeTypes: CoreDataChangeTypes = .all
  31. ) -> some Publisher<Set<NSManagedObjectID>, Never> {
  32. Foundation.NotificationCenter.default
  33. .publisher(for: .NSManagedObjectContextDidSave)
  34. .compactMap { notification -> Set<NSManagedObjectID>? in
  35. var objectIDs = Set<NSManagedObjectID>()
  36. // Process inserted objects if requested
  37. if changeTypes.contains(.inserted) {
  38. objectIDs.formUnion(notification.insertedObjectIDs)
  39. }
  40. // Process updated objects if requested
  41. if changeTypes.contains(.updated) {
  42. objectIDs.formUnion(notification.updatedObjectIDs)
  43. }
  44. // Process deleted objects if requested
  45. if changeTypes.contains(.deleted) {
  46. objectIDs.formUnion(notification.deletedObjectIDs)
  47. }
  48. // Only emit non-empty sets
  49. return objectIDs.isEmpty ? nil : objectIDs
  50. }
  51. }
  52. extension Publisher where Output == Set<NSManagedObjectID> {
  53. /// Filters Core Data changes by entity name
  54. ///
  55. /// This method allows filtering Core Data changes by entity name.
  56. ///
  57. /// Example usage:
  58. /// ```swift
  59. /// // Filter changes for "GlucoseStored" entity
  60. /// publisher.filteredByEntityName("GlucoseStored")
  61. /// ```
  62. ///
  63. /// - Parameters:
  64. /// - name: The name of the Core Data entity to filter for
  65. /// - Returns: A publisher emitting filtered sets of NSManagedObjectIDs
  66. func filteredByEntityName(
  67. _ name: String
  68. ) -> some Publisher<Set<NSManagedObjectID>, Self.Failure> {
  69. compactMap { objectIDs -> Set<NSManagedObjectID>? in
  70. // Early exit for empty sets
  71. guard !objectIDs.isEmpty else { return nil }
  72. // Use lazy evaluation for better performance
  73. let filtered = objectIDs.lazy.filter { $0.entity.name == name }
  74. let result = Set(filtered)
  75. return result.isEmpty ? nil : result
  76. }
  77. }
  78. }
  79. extension Notification {
  80. var insertedObjectIDs: Set<NSManagedObjectID> {
  81. guard let objects = userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject> else { return [] }
  82. return Set(objects.lazy.map(\.objectID))
  83. }
  84. var updatedObjectIDs: Set<NSManagedObjectID> {
  85. guard let objects = userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject> else { return [] }
  86. return Set(objects.lazy.map(\.objectID))
  87. }
  88. var deletedObjectIDs: Set<NSManagedObjectID> {
  89. guard let objects = userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject> else { return [] }
  90. return Set(objects.lazy.map(\.objectID))
  91. }
  92. }