Przeglądaj źródła

refactoring for concurrency reasons ...still wip

polscm32 2 lat temu
rodzic
commit
65d135985a

+ 49 - 35
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -90,24 +90,44 @@ final class OpenAPS {
     }
 
     // fetch glucose to pass it to the meal function and to determine basal
-    private func fetchGlucose() -> [GlucoseStored]? {
-        CoreDataStack.shared.fetchEntities(
-            ofType: GlucoseStored.self,
-            predicate: NSPredicate.predicateForSixHoursAgo,
-            key: "date",
-            ascending: false,
-            fetchLimit: 72,
-            batchSize: 24
-        )
+    private func fetchAndProcessGlucose() -> String {
+        var glucoseAsJSON: String?
+
+        context.performAndWait {
+            let results = CoreDataStack.shared.fetchEntities(
+                ofType: GlucoseStored.self,
+                predicate: NSPredicate.predicateForSixHoursAgo,
+                key: "date",
+                ascending: false,
+                fetchLimit: 72,
+                batchSize: 24
+            )
+
+            // convert to json
+            glucoseAsJSON = self.jsonConverter.convertToJSON(results)
+        }
+
+        return glucoseAsJSON ?? "{}"
     }
 
-    private func fetchCarbs() -> [CarbEntryStored]? {
-        CoreDataStack.shared.fetchEntities(
-            ofType: CarbEntryStored.self,
-            predicate: NSPredicate.predicateForOneDayAgo,
-            key: "date",
-            ascending: false
-        )
+    private func fetchAndProcessCarbs() -> String {
+        // perform fetch AND conversion on the same thread
+        // if we do it like this we do not change the thread and do not have to pass the objectIDs
+        var carbsAsJSON: String?
+
+        context.performAndWait {
+            let results = CoreDataStack.shared.fetchEntities(
+                ofType: CarbEntryStored.self,
+                predicate: NSPredicate.predicateForOneDayAgo,
+                key: "date",
+                ascending: false
+            )
+
+            // convert to json
+            carbsAsJSON = self.jsonConverter.convertToJSON(results)
+        }
+
+        return carbsAsJSON ?? "{}"
     }
 
     private func fetchPumpHistoryObjectIDs() -> [NSManagedObjectID]? {
@@ -170,12 +190,10 @@ final class OpenAPS {
                 let pumpHistoryJSON = self.parsePumpHistory(pumpHistoryObjectIDs)
 
                 // carbs
-                let carbs = self.fetchCarbs()
-                let carbsString = self.jsonConverter.convertToJSON(carbs)
+                let carbsAsJSON = self.fetchAndProcessCarbs()
 
                 /// glucose
-                let glucose = self.fetchGlucose()
-                let glucoseString = self.jsonConverter.convertToJSON(glucose)
+                let glucoseAsJSON = self.fetchAndProcessGlucose()
 
                 /// profile
                 let profile = self.loadFileFromStorage(name: Settings.profile)
@@ -187,8 +205,8 @@ final class OpenAPS {
                     profile: profile,
                     basalProfile: basalProfile,
                     clock: pass,
-                    carbs: carbsString,
-                    glucose: glucoseString
+                    carbs: carbsAsJSON,
+                    glucose: glucoseAsJSON
                 )
                 self.storage.save(meal, as: Monitor.meal)
 
@@ -212,7 +230,7 @@ final class OpenAPS {
                 let oref2_variables = self.oref2()
 
                 let orefDetermination = self.determineBasal(
-                    glucose: glucoseString,
+                    glucose: glucoseAsJSON,
                     currentTemp: tempBasal,
                     iob: iob,
                     profile: profile,
@@ -459,22 +477,20 @@ final class OpenAPS {
                 let pumpHistoryJSON = self.parsePumpHistory(pumpHistoryObjectIDs)
 
                 // carbs
-                let carbs = self.fetchCarbs()
-                let carbsString = self.jsonConverter.convertToJSON(carbs)
+                let carbsAsJSON = self.fetchAndProcessCarbs()
 
                 /// glucose
-                let glucose = self.fetchGlucose()
-                let glucoseString = self.jsonConverter.convertToJSON(glucose)
+                let glucoseAsJSON = self.fetchAndProcessGlucose()
 
                 let profile = self.loadFileFromStorage(name: Settings.profile)
                 let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
                 let tempTargets = self.loadFileFromStorage(name: Settings.tempTargets)
                 let autosensResult = self.autosense(
-                    glucose: glucoseString,
+                    glucose: glucoseAsJSON,
                     pumpHistory: pumpHistoryJSON,
                     basalprofile: basalProfile,
                     profile: profile,
-                    carbs: carbsString,
+                    carbs: carbsAsJSON,
                     temptargets: tempTargets
                 )
 
@@ -500,22 +516,20 @@ final class OpenAPS {
                 let pumpHistoryJSON = self.parsePumpHistory(pumpHistoryObjectIDs)
 
                 /// glucose
-                let glucose = self.fetchGlucose()
-                let glucoseString = self.jsonConverter.convertToJSON(glucose)
+                let glucoseAsJSON = self.fetchAndProcessGlucose()
 
                 let profile = self.loadFileFromStorage(name: Settings.profile)
                 let pumpProfile = self.loadFileFromStorage(name: Settings.pumpProfile)
 
                 // carbs
-                let carbs = self.fetchCarbs()
-                let carbsString = self.jsonConverter.convertToJSON(carbs)
+                let carbsAsJSON = self.fetchAndProcessCarbs()
 
                 let autotunePreppedGlucose = self.autotunePrepare(
                     pumphistory: pumpHistoryJSON,
                     profile: profile,
-                    glucose: glucoseString,
+                    glucose: glucoseAsJSON,
                     pumpprofile: pumpProfile,
-                    carbs: carbsString,
+                    carbs: carbsAsJSON,
                     categorizeUamAsBasal: categorizeUamAsBasal,
                     tuneInsulinCurve: tuneInsulinCurve
                 )

+ 42 - 69
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -104,11 +104,9 @@ extension Bolus {
 
         override func subscribe() {
             setupNotification()
-            Task {
-                await updateGlucose()
-                await updateDetermination()
-                await setupInsulinRequired()
-            }
+            updateGlucose()
+            updateDetermination()
+
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(BolusFailureObserver.self, observer: self)
             units = settingsManager.settings.units
@@ -212,65 +210,59 @@ extension Bolus {
             let determinationUpdates = objects.filter { $0 is OrefDetermination }
 
             if glucoseUpdates.isNotEmpty {
-                await updateGlucose()
+                updateGlucose()
             }
             if determinationUpdates.isNotEmpty {
-                await updateDetermination()
+                updateDetermination()
             }
         }
 
         // MARK: - Glucose
 
-        private func fetchGlucose() async -> [GlucoseStored] {
-            await withCheckedContinuation { continuation in
-                backgroundContext.perform {
-                    let results = CoreDataStack.shared.fetchEntities(
-                        ofType: GlucoseStored.self,
-                        predicate: NSPredicate.predicateFor30MinAgo,
-                        key: "date",
-                        ascending: false,
-                        fetchLimit: 3
-                    )
-                    continuation.resume(returning: results)
-                }
-            }
-        }
-
-        private func updateGlucose() async {
-            let results = await fetchGlucose()
-            await MainActor.run {
-                glucoseFromPersistence = results
-                let lastGlucose = glucoseFromPersistence.first?.glucose ?? 0
-                let thirdLastGlucose = glucoseFromPersistence.last?.glucose ?? 0
+        private func updateGlucose() {
+            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
+                ofType: GlucoseStored.self,
+                predicate: NSPredicate.predicateFor30MinAgo,
+                key: "date",
+                ascending: false,
+                fetchLimit: 3
+            ) { fetchedValues in
+                self.glucoseFromPersistence = fetchedValues
+
+                let lastGlucose = self.glucoseFromPersistence.first?.glucose ?? 0
+                let thirdLastGlucose = self.glucoseFromPersistence.last?.glucose ?? 0
                 let delta = Decimal(lastGlucose) - Decimal(thirdLastGlucose)
 
-                currentBG = Decimal(lastGlucose)
-                deltaBG = delta
+                self.currentBG = Decimal(lastGlucose)
+                self.deltaBG = delta
             }
-            await setupInsulinRequired()
         }
 
-        private func fetchDetermination() async -> [OrefDetermination] {
-            await withCheckedContinuation { continuation in
-                backgroundContext.perform {
-                    let results = CoreDataStack.shared.fetchEntities(
-                        ofType: OrefDetermination.self,
-                        predicate: NSPredicate.enactedDetermination,
-                        key: "deliverAt",
-                        ascending: false,
-                        fetchLimit: 1
-                    )
-                    continuation.resume(returning: results)
-                }
-            }
-        }
+        private func updateDetermination() {
+            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
+                ofType: OrefDetermination.self,
+                predicate: NSPredicate.enactedDetermination,
+                key: "deliverAt",
+                ascending: false,
+                fetchLimit: 1
+            ) { fetchedValues in
+                guard let mostRecentDetermination = fetchedValues.first else { return }
+                self.determination = fetchedValues
+
+                // setup vars for bolus calculation
+                self.insulinRequired = (mostRecentDetermination.insulinReq ?? 0) as Decimal
+                self.evBG = (mostRecentDetermination.eventualBG ?? 0) as Decimal
+                self.insulin = (mostRecentDetermination.insulinForManualBolus ?? 0) as Decimal
+                self.target = (mostRecentDetermination.currentTarget ?? 100) as Decimal
+                self.isf = (mostRecentDetermination.insulinSensitivity ?? 0) as Decimal
+                self.cob = mostRecentDetermination.cob as Int16
+                self.iob = (mostRecentDetermination.iob ?? 0) as Decimal
+                self.basal = (mostRecentDetermination.tempBasal ?? 0) as Decimal
+                self.carbRatio = (mostRecentDetermination.carbRatio ?? 0) as Decimal
 
-        private func updateDetermination() async {
-            let results = await fetchDetermination()
-            await MainActor.run {
-                determination = results
+                self.getCurrentBasal()
+                self.insulinCalculated = self.calculateInsulin()
             }
-            await setupInsulinRequired()
         }
 
         // MARK: CALCULATIONS FOR THE BOLUS CALCULATOR
@@ -335,22 +327,6 @@ extension Bolus {
             return apsManager.roundBolus(amount: insulinCalculated)
         }
 
-        func setupInsulinRequired() async {
-            await MainActor.run {
-                self.insulinRequired = (self.determination.first?.insulinReq ?? 0) as Decimal
-                self.evBG = (self.determination.first?.eventualBG ?? 0) as Decimal
-                self.insulin = (self.determination.first?.insulinForManualBolus ?? 0) as Decimal
-                self.target = (self.determination.first?.currentTarget ?? 100) as Decimal
-                self.isf = (self.determination.first?.insulinSensitivity ?? 0) as Decimal
-                self.iob = (self.determination.first?.iob ?? 0) as Decimal
-                self.cob = (self.determination.first?.cob ?? 0) as Int16
-                self.basal = (self.determination.first?.tempBasal ?? 0) as Decimal
-                self.carbRatio = (self.determination.first?.carbRatio ?? 0) as Decimal
-                self.getCurrentBasal()
-                self.insulinCalculated = self.calculateInsulin()
-            }
-        }
-
         // MARK: - Button tasks
 
         @MainActor func invokeTreatmentsTask() {
@@ -665,9 +641,6 @@ extension Bolus.StateModel: DeterminationObserver, BolusFailureObserver {
                 self.hideModal()
             }
         }
-        Task {
-            await setupInsulinRequired()
-        }
     }
 
     func bolusDidFail() {

+ 49 - 85
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -24,7 +24,8 @@ extension Home {
         @Published var basalProfile: [BasalProfileEntry] = []
         @Published var tempTargets: [TempTarget] = []
         @Published var glucoseFromPersistence: [GlucoseStored] = []
-        @Published var determinationsFromPersistence: [NSManagedObjectID] = []
+        @Published var determinationsFromCoreData: [OrefDetermination] = []
+        @Published var mostRecentDetermination: OrefDetermination?
         @Published var carbsFromPersistence: [CarbEntryStored] = []
         @Published var fpusFromPersistence: [CarbEntryStored] = []
         @Published var timerDate = Date()
@@ -85,13 +86,10 @@ extension Home {
             setupAnnouncements()
             setupCurrentPumpTimezone()
             setupNotification()
-
-            Task {
-                await updateGlucose()
-                await updateDetermination()
-                await updateCarbs()
-                await updateFpus()
-            }
+            updateGlucose()
+            updateDetermination()
+            updateCarbs()
+            updateFPUs()
 
             uploadStats = settingsManager.settings.uploadStats
             units = settingsManager.settings.units
@@ -235,99 +233,65 @@ extension Home {
             let carbUpdates = objects.filter { $0 is CarbEntryStored }
 
             if glucoseUpdates.isNotEmpty {
-                await updateGlucose()
+                updateGlucose()
             }
             if determinationUpdates.isNotEmpty {
-                await updateDetermination()
+                updateDetermination()
             }
             if carbUpdates.isNotEmpty {
-                await updateCarbs()
-                await updateFpus()
-            }
-        }
-
-        /// wait for the fetch to complete and then update the UI on the main thread
-        private func updateGlucose() async {
-            let results = await fetchGlucoseInBackground()
-            await MainActor.run {
-                glucoseFromPersistence = results
-            }
-        }
-
-        private func updateDetermination() async {
-            let results = await fetchDeterminationInBackground()
-            let ids = results.map(\.objectID)
-            await MainActor.run {
-                determinationsFromPersistence = ids
-            }
-        }
-
-        private func updateCarbs() async {
-            let results = await fetchCarbsInBackground()
-            await MainActor.run {
-                carbsFromPersistence = results
+                updateCarbs()
+                updateFPUs()
             }
         }
 
-        private func updateFpus() async {
-            let results = await fetchFpusInBackground()
-            await MainActor.run {
-                fpusFromPersistence = results
+        private func updateGlucose() {
+            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
+                ofType: GlucoseStored.self,
+                predicate: NSPredicate.predicateForOneDayAgo,
+                key: "date",
+                ascending: false,
+                fetchLimit: 288,
+                batchSize: 50
+            ) { fetchedValues in
+                self.glucoseFromPersistence = fetchedValues
             }
         }
 
-        /// do the heavy fetch operation in the background
-        private func fetchGlucoseInBackground() async -> [GlucoseStored] {
-            await withCheckedContinuation { continuation in
-                context.perform {
-                    let results = self.provider.fetchGlucose()
-                    continuation.resume(returning: results)
-                }
+        private func updateDetermination() {
+            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
+                ofType: OrefDetermination.self,
+                predicate: NSPredicate.enactedDetermination,
+                key: "deliverAt",
+                ascending: false,
+                fetchLimit: 1
+            ) { fetchedValues in
+                guard let latestDetermination = fetchedValues.first else { return }
+                self.determinationsFromCoreData = fetchedValues
+                self.mostRecentDetermination = latestDetermination
             }
         }
 
-        private func fetchDeterminationInBackground() async -> [OrefDetermination] {
-            await withCheckedContinuation { continuation in
-                context.perform {
-                    let results = CoreDataStack.shared.fetchEntities(
-                        ofType: OrefDetermination.self,
-                        predicate: NSPredicate.enactedDetermination,
-                        key: "deliverAt",
-                        ascending: false,
-                        fetchLimit: 1
-                    )
-                    continuation.resume(returning: results)
-                }
+        private func updateCarbs() {
+            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
+                ofType: CarbEntryStored.self,
+                predicate: NSPredicate.carbsForChart,
+                key: "date",
+                ascending: false,
+                batchSize: 20
+            ) { fetchedValues in
+                self.carbsFromPersistence = fetchedValues
             }
         }
 
-        private func fetchCarbsInBackground() async -> [CarbEntryStored] {
-            await withCheckedContinuation { continuation in
-                context.perform {
-                    let results = CoreDataStack.shared.fetchEntities(
-                        ofType: CarbEntryStored.self,
-                        predicate: NSPredicate.carbsForChart,
-                        key: "date",
-                        ascending: false,
-                        batchSize: 20
-                    )
-                    continuation.resume(returning: results)
-                }
-            }
-        }
-
-        private func fetchFpusInBackground() async -> [CarbEntryStored] {
-            await withCheckedContinuation { continuation in
-                context.perform {
-                    let results = CoreDataStack.shared.fetchEntities(
-                        ofType: CarbEntryStored.self,
-                        predicate: NSPredicate.fpusForChart,
-                        key: "date",
-                        ascending: false,
-                        batchSize: 20
-                    )
-                    continuation.resume(returning: results)
-                }
+        private func updateFPUs() {
+            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
+                ofType: CarbEntryStored.self,
+                predicate: NSPredicate.fpusForChart,
+                key: "date",
+                ascending: false,
+                batchSize: 20
+            ) { fetchedValues in
+                self.fpusFromPersistence = fetchedValues
             }
         }
 

+ 5 - 2
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -158,7 +158,7 @@ struct MainChartView: View {
                         yAxisChartData()
                         scroller.scrollTo("MainChart", anchor: .trailing)
                     }
-                    .onChange(of: state.determinationsFromPersistence) { _ in
+                    .onChange(of: state.determinationsFromCoreData) { _ in
                         updateStartEndMarkers()
                         scroller.scrollTo("MainChart", anchor: .trailing)
                     }
@@ -500,7 +500,10 @@ extension MainChartView {
     }
 
     private func preprocessForecastData() -> [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] {
-        state.determinationsFromPersistence
+        state.determinationsFromCoreData
+            .compactMap { determination -> NSManagedObjectID? in
+                determination.objectID
+            }
             .flatMap { determinationID -> [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] in
                 let context = CoreDataStack.shared.viewContext
                 let forecasts = getForecasts(for: determinationID, in: context)

+ 43 - 47
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -145,23 +145,23 @@ extension Home {
             }
         }
 
-        private var determination: OrefDetermination? {
-            guard let determinationObjectID = state.determinationsFromPersistence.first else {
-                return nil
-            }
-            return CoreDataStack.shared.backgroundContext.object(with: determinationObjectID) as? OrefDetermination
-        }
-
-        private var determinationAsBinding: Binding<OrefDetermination?> {
-            Binding<OrefDetermination?>(
-                get: {
-                    guard let determinationObjectID = state.determinationsFromPersistence.first else {
-                        return nil
-                    }
-                    return CoreDataStack.shared.backgroundContext.object(with: determinationObjectID) as? OrefDetermination
-                }, set: { _ in }
-            )
-        }
+//        private var determination: OrefDetermination? {
+//            guard let determinationObjectID = state.determinationsFromPersistence.first else {
+//                return nil
+//            }
+//            return CoreDataStack.shared.backgroundContext.object(with: determinationObjectID) as? OrefDetermination
+//        }
+
+//        private var determinationAsBinding: Binding<OrefDetermination?> {
+//            Binding<OrefDetermination?>(
+//                get: {
+//                    guard let determinationObjectID = state.determinationsFromPersistence.first else {
+//                        return nil
+//                    }
+//                    return CoreDataStack.shared.backgroundContext.object(with: determinationObjectID) as? OrefDetermination
+//                }, set: { _ in }
+//            )
+//        }
 
         var glucoseView: some View {
             CurrentGlucoseView(
@@ -438,7 +438,7 @@ extension Home {
             VStack(alignment: .leading, spacing: 20) {
                 /// Loop view at bottomLeading
                 LoopView(
-                    determination: determinationAsBinding,
+                    determination: $state.mostRecentDetermination,
                     closedLoop: $state.closedLoop,
                     timerDate: $state.timerDate,
                     isLooping: $state.isLooping,
@@ -454,7 +454,7 @@ extension Home {
                 }
                 /// eventualBG string at bottomTrailing
 
-                if let eventualBG = determination?.eventualBG {
+                if let eventualBG = state.determinationsFromCoreData.first?.eventualBG {
                     let bg = eventualBG as Decimal
                     HStack {
                         Image(systemName: "arrow.right.circle")
@@ -502,7 +502,7 @@ extension Home {
                         .font(.system(size: 16))
                         .foregroundColor(Color.insulin)
                     Text(
-                        (numberFormatter.string(from: (determination?.iob ?? 0) as NSNumber) ?? "0") +
+                        (numberFormatter.string(from: (state.mostRecentDetermination?.iob ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
                     .font(.system(size: 16, weight: .bold, design: .rounded))
@@ -515,7 +515,7 @@ extension Home {
                         .font(.system(size: 16))
                         .foregroundColor(.loopYellow)
                     Text(
-                        (numberFormatter.string(from: (determination?.cob ?? 0) as NSNumber) ?? "0") +
+                        (numberFormatter.string(from: (state.mostRecentDetermination?.cob ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" g", comment: "gram of carbs")
                     )
                     .font(.system(size: 16, weight: .bold, design: .rounded))
@@ -538,7 +538,11 @@ extension Home {
                 if !state.tins {
                     Spacer()
                     Text(
-                        "TDD: " + (numberFormatter.string(from: (determination?.totalDailyDose ?? 0) as NSNumber) ?? "0") +
+                        "TDD: " +
+                            (
+                                numberFormatter
+                                    .string(from: (state.mostRecentDetermination?.totalDailyDose ?? 0) as NSNumber) ?? "0"
+                            ) +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
                     .font(.system(size: 16, weight: .bold, design: .rounded))
@@ -822,7 +826,7 @@ extension Home {
             ZStack(alignment: .bottom) {
                 TabView {
                     let carbsRequiredBadge: String? = {
-                        guard let carbsRequired = determination?.carbsRequired as? Decimal else { return nil }
+                        guard let carbsRequired = state.mostRecentDetermination?.carbsRequired as? Decimal else { return nil }
                         if carbsRequired > state.settingsManager.settings.carbsRequiredThreshold {
                             let numberAsNSNumber = NSDecimalNumber(decimal: carbsRequired)
                             let formattedNumber = numberFormatter.string(from: numberAsNSNumber) ?? ""
@@ -885,21 +889,17 @@ extension Home {
             VStack(alignment: .leading, spacing: 4) {
                 Text(statusTitle).font(.headline).foregroundColor(.white)
                     .padding(.bottom, 4)
-                if let determinationObjectID = state.determinationsFromPersistence.first {
-                    if let determination = CoreDataStack.shared.backgroundContext
-                        .object(with: determinationObjectID) as? OrefDetermination
-                    {
-                        if determination.glucose == 400 {
-                            Text("Invalid CGM reading (HIGH).").font(.callout).bold().foregroundColor(.loopRed).padding(.top, 8)
-                            Text("SMBs and High Temps Disabled.").font(.caption).foregroundColor(.white).padding(.bottom, 4)
-                        } else {
-                            TagCloudView(tags: determination.reasonParts).animation(.none, value: false)
-
-                            Text(determination.reasonConclusion.capitalizingFirstLetter()).font(.caption).foregroundColor(.white)
-                        }
+                if let determination = state.determinationsFromCoreData.first {
+                    if determination.glucose == 400 {
+                        Text("Invalid CGM reading (HIGH).").font(.callout).bold().foregroundColor(.loopRed).padding(.top, 8)
+                        Text("SMBs and High Temps Disabled.").font(.caption).foregroundColor(.white).padding(.bottom, 4)
                     } else {
-                        Text("No determination found").font(.body).foregroundColor(.white)
+                        TagCloudView(tags: determination.reasonParts).animation(.none, value: false)
+
+                        Text(determination.reasonConclusion.capitalizingFirstLetter()).font(.caption).foregroundColor(.white)
                     }
+                } else {
+                    Text("No determination found").font(.body).foregroundColor(.white)
                 }
 
                 if let errorMessage = state.errorMessage, let date = state.errorDate {
@@ -914,17 +914,13 @@ extension Home {
         }
 
         private func setStatusTitle() {
-            if let determinationObjectID = state.determinationsFromPersistence.first {
-                if let determination = CoreDataStack.shared.backgroundContext
-                    .object(with: determinationObjectID) as? OrefDetermination
-                {
-                    let dateFormatter = DateFormatter()
-                    dateFormatter.timeStyle = .short
-                    statusTitle = NSLocalizedString("Oref Determination enacted at", comment: "Headline in enacted pop up") +
-                        " " +
-                        dateFormatter
-                        .string(from: determination.deliverAt ?? Date())
-                }
+            if let determination = state.determinationsFromCoreData.first {
+                let dateFormatter = DateFormatter()
+                dateFormatter.timeStyle = .short
+                statusTitle = NSLocalizedString("Oref Determination enacted at", comment: "Headline in enacted pop up") +
+                    " " +
+                    dateFormatter
+                    .string(from: determination.deliverAt ?? Date())
             } else {
                 statusTitle = "No Oref determination"
                 return

+ 46 - 0
Model/CoreDataStack.swift

@@ -159,6 +159,52 @@ class CoreDataStack: ObservableObject {
         }
     }
 
+    // fetch and only return a NSManagedObjectID
+    func fetchNSManagedObjectID<T: NSManagedObject>(
+        ofType type: T.Type,
+        predicate: NSPredicate,
+        key: String,
+        ascending: Bool,
+        fetchLimit: Int? = nil,
+        batchSize: Int? = nil,
+        propertiesToFetch: [String]? = nil,
+        callingFunction: String = #function,
+        callingClass: String = #fileID,
+        completion: @escaping ([NSManagedObjectID]) -> Void
+    ) {
+        let request = NSFetchRequest<NSManagedObjectID>(entityName: String(describing: type))
+        request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
+        request.predicate = predicate
+        request.resultType = .managedObjectIDResultType
+        if let limit = fetchLimit {
+            request.fetchLimit = limit
+        }
+        if let batchSize = batchSize {
+            request.fetchBatchSize = batchSize
+        }
+        if let propertiesToFetch = propertiesToFetch {
+            request.propertiesToFetch = propertiesToFetch
+        }
+
+        // Perform fetch in the background
+        backgroundContext.perform {
+            var result: [NSManagedObjectID]?
+
+            do {
+                debugPrint(
+                    "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on thread \(Thread.current)"
+                )
+                result = try self.backgroundContext.fetch(request)
+            } catch let error as NSError {
+                debugPrint(
+                    "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on thread \(Thread.current)"
+                )
+            }
+
+            completion(result ?? [])
+        }
+    }
+
     // MARK: - Save
 
     //

+ 2 - 0
Model/Helper/GlucoseStored+helper.swift

@@ -57,6 +57,8 @@ extension GlucoseStored: Encodable {
         var container = encoder.container(keyedBy: CodingKeys.self)
 
         let dateFormatter = ISO8601DateFormatter()
+        dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
+
         try container.encode(dateFormatter.string(from: date ?? Date()), forKey: .dateString)
 
         let dateAsUnixTimestamp = String(format: "%.0f", (date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000)

+ 5 - 1
Model/Helper/PumpEvent+helper.swift

@@ -110,7 +110,11 @@ enum PumpEventDTO: Encodable {
 
 // Extension with helper functions to map pump events to DTO objects via uniform masking enum
 extension PumpEventStored {
-    static let dateFormatter = ISO8601DateFormatter()
+    static let dateFormatter: ISO8601DateFormatter = {
+        let formatter = ISO8601DateFormatter()
+        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
+        return formatter
+    }()
 
     func toBolusDTOEnum() -> PumpEventDTO? {
         guard let id = id, let timestamp = timestamp, let bolus = bolus, let amount = bolus.amount else {