Просмотр исходного кода

core data concurrency refactoring ...wip

polscm32 2 лет назад
Родитель
Сommit
f6f5065024

+ 6 - 0
FreeAPS.xcodeproj/xcshareddata/xcschemes/FreeAPS X.xcscheme

@@ -60,6 +60,12 @@
             ReferencedContainer = "container:FreeAPS.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
+      <CommandLineArguments>
+         <CommandLineArgument
+            argument = "-com.apple.CoreData.ConcurrencyDebug 4"
+            isEnabled = "YES">
+         </CommandLineArgument>
+      </CommandLineArguments>
       <EnvironmentVariables>
          <EnvironmentVariable
             key = "CG_NUMERICS_SHOW_BACKTRACE"

+ 22 - 22
FreeAPS/Sources/APS/APSManager.swift

@@ -329,7 +329,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
     func determineBasal() -> AnyPublisher<Bool, Never> {
         debug(.apsManager, "Start determine basal")
-        let glucose = fetchGlucoseData(forPeriod: "30min", withPredicate: NSPredicate.predicateFor30MinAgo, fetchLimit: 4)
+        let glucose = fetchGlucose(predicate: NSPredicate.predicateFor30MinAgo, fetchLimit: 4)
         guard glucose.count > 2 else {
             debug(.apsManager, "Not enough glucose data")
             processError(APSError.glucoseError(message: "Not enough glucose data"))
@@ -911,23 +911,15 @@ final class BaseAPSManager: APSManager, Injectable {
     }
 
     // fetch glucose for time interval
-    private func fetchGlucoseData(
-        forPeriod period: String,
-        withPredicate predicate: NSPredicate,
-        fetchLimit: Int? = nil
-    ) -> [GlucoseStored] {
-        do {
-            let fetchedData = try privateContext.fetch(GlucoseStored.fetch(predicate, ascending: false, fetchLimit: fetchLimit))
-            debugPrint(
-                "APSManager: \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) fetched glucose for \(period) period"
-            )
-            return fetchedData
-        } catch {
-            debugPrint(
-                "APSManager: \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) error while fetching glucose for \(period) period"
-            )
-            return []
-        }
+    func fetchGlucose(predicate: NSPredicate, fetchLimit: Int? = nil, batchSize: Int? = nil) -> [GlucoseStored] {
+        CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            predicate: predicate,
+            key: "date",
+            ascending: false,
+            fetchLimit: fetchLimit,
+            batchSize: batchSize
+        )
     }
 
     // Add to statistics.JSON for upload to NS.
@@ -1043,10 +1035,18 @@ final class BaseAPSManager: APSManager, Injectable {
                 }
 
                 // Glucose Values
-                let glucose24h = fetchGlucoseData(forPeriod: "24h", withPredicate: NSPredicate.predicateForOneDayAgo)
-                let glucoseOneWeek = fetchGlucoseData(forPeriod: "7d", withPredicate: NSPredicate.predicateForOneWeek)
-                let glucoseOneMonth = fetchGlucoseData(forPeriod: "30d", withPredicate: NSPredicate.predicateForOneMonth)
-                let glucoseThreeMonths = fetchGlucoseData(forPeriod: "90d", withPredicate: NSPredicate.predicateForThreeMonths)
+                let glucose24h = fetchGlucose(predicate: NSPredicate.predicateForOneDayAgo, fetchLimit: 288, batchSize: 50)
+                let glucoseOneWeek = fetchGlucose(predicate: NSPredicate.predicateForOneWeek, fetchLimit: 288 * 7, batchSize: 250)
+                let glucoseOneMonth = fetchGlucose(
+                    predicate: NSPredicate.predicateForOneMonth,
+                    fetchLimit: 288 * 7 * 30,
+                    batchSize: 500
+                )
+                let glucoseThreeMonths = fetchGlucose(
+                    predicate: NSPredicate.predicateForThreeMonths,
+                    fetchLimit: 288 * 7 * 30 * 3,
+                    batchSize: 1000
+                )
 
                 // First date
                 let previous = glucoseThreeMonths.last?.date ?? Date()

+ 2 - 0
FreeAPS/Sources/APS/CGM/GlucoseSimulatorSource.swift

@@ -182,6 +182,8 @@ class IntelligentGenerator: BloodGlucoseGenerator {
     }
 
     private func makeStepInTrend() {
+        guard trendStepsLeft > 0 else { return }
+
         currentGlucose +=
             Int(Double((trendTargetValue - currentGlucose) / trendStepsLeft) * [0.3, 0.6, 1, 1.3, 1.6, 2.0].randomElement()!)
         trendStepsLeft -= 1

+ 7 - 7
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -347,14 +347,14 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
             display: pumpManager.status.pumpBatteryChargeRemaining != nil
         )
 
-        let batteryToStore = OpenAPS_Battery(context: privateContext)
-        batteryToStore.id = UUID()
-        batteryToStore.date = Date()
-        batteryToStore.percent = Int16(batteryPercent)
-        batteryToStore.voltage = nil
-        batteryToStore.status = batteryPercent > 10 ? "normal" : "low"
-        batteryToStore.display = status.pumpBatteryChargeRemaining != nil
         privateContext.perform {
+            let batteryToStore = OpenAPS_Battery(context: self.privateContext)
+            batteryToStore.id = UUID()
+            batteryToStore.date = Date()
+            batteryToStore.percent = Int16(batteryPercent)
+            batteryToStore.voltage = nil
+            batteryToStore.status = batteryPercent > 10 ? "normal" : "low"
+            batteryToStore.display = status.pumpBatteryChargeRemaining != nil
             if self.privateContext.hasChanges {
                 do {
                     try self.privateContext.save()

+ 62 - 74
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -164,8 +164,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     func syncDate() -> Date {
-        //  TODO: - proof logic here!
-        fetchGlucose().first?.date ?? .distantPast
+        coredataContext.performAndWait {
+            fetchGlucose().first?.date ?? .distantPast
+        }
     }
 
     func lastGlucoseDate() -> Date {
@@ -199,72 +200,60 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     /// also tried this but here again you need to make everything asynchronous...
     ///  let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
     /// privateContext.parent = coredataContext /// merges changes to the core data context
+    ///
     func fetchGlucose() -> [GlucoseStored] {
-        do {
-            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
-            return try coredataContext.fetch(GlucoseStored.fetch(
-                NSPredicate.predicateForOneDayAgo,
-                ascending: false,
-                fetchLimit: 288,
-                batchSize: 50
-            ))
-        } catch {
-            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
-            return []
-        }
+        let predicate = NSPredicate.predicateForOneDayAgo
+        return CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            predicate: predicate,
+            key: "date",
+            ascending: false,
+            fetchLimit: 288,
+            batchSize: 50
+        )
     }
 
-    private func fetchLatestGlucose() -> GlucoseStored? {
-        do {
-            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
-            return try coredataContext.fetch(GlucoseStored.fetch(
-                NSPredicate.predicateFor20MinAgo,
-                ascending: false,
-                fetchLimit: 1
-            )).first
-        } catch {
-            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
-            return nil
-        }
+    func fetchManualGlucose() -> [GlucoseStored] {
+        let predicate = NSPredicate.manualGlucose
+        return CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            predicate: predicate,
+            key: "date",
+            ascending: false,
+            fetchLimit: 288,
+            batchSize: 50
+        )
     }
 
-    private func fetchAndProcessManualGlucose() -> [BloodGlucose] {
-        do {
-            let fetchedResults = try coredataContext.fetch(GlucoseStored.fetch(
-                NSPredicate.manualGlucose,
-                ascending: false,
-                fetchLimit: 288,
-                batchSize: 50
-            ))
-            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched manual glucose")
-            let glucoseArray = fetchedResults.map { result in
-                BloodGlucose(
-                    date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
-                    dateString: result.date ?? Date(),
-                    unfiltered: Decimal(result.glucose),
-                    filtered: Decimal(result.glucose),
-                    noise: nil,
-                    type: ""
-                )
-            }
-            return glucoseArray
-        } catch {
-            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch manual glucose")
-            return []
-        }
+    func fetchLatestGlucose() -> GlucoseStored? {
+        let predicate = NSPredicate.predicateFor20MinAgo
+        return CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            predicate: predicate,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1
+        ).first
     }
 
-    private func fetchAndProcessGlucose() -> [BloodGlucose] {
-        do {
-            let results = try coredataContext.fetch(GlucoseStored.fetch(
-                NSPredicate.predicateForOneDayAgo,
-                ascending: false,
-                fetchLimit: 288,
-                batchSize: 50
-            ))
-
-            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+    private func processManualGlucose() -> [BloodGlucose] {
+        let fetchedResults = fetchManualGlucose()
+        let glucoseArray = fetchedResults.map { result in
+            BloodGlucose(
+                date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
+                dateString: result.date ?? Date(),
+                unfiltered: Decimal(result.glucose),
+                filtered: Decimal(result.glucose),
+                noise: nil,
+                type: ""
+            )
+        }
+        return glucoseArray
+    }
 
+    private func processGlucose() -> [BloodGlucose] {
+        coredataContext.performAndWait {
+            let results = self.fetchGlucose()
             let glucoseArray = results.map { result in
                 BloodGlucose(
                     date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
@@ -276,15 +265,12 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                 )
             }
             return glucoseArray
-        } catch {
-            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
-            return []
         }
     }
 
     func nightscoutGlucoseNotUploaded() -> [BloodGlucose] {
         let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? []
-        let recentGlucose = fetchAndProcessGlucose()
+        let recentGlucose = processGlucose()
 
         return Array(Set(recentGlucose).subtracting(Set(uploaded)))
     }
@@ -299,7 +285,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         let uploaded = (storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? [])
             .filter({ $0.type == GlucoseType.manual.rawValue })
 
-        let recent = fetchAndProcessManualGlucose()
+        let recent = processManualGlucose()
         let filtered = Array(Set(recent).subtracting(Set(uploaded)))
         let manualReadings = filtered.map { item -> NigtscoutTreatment in
             NigtscoutTreatment(
@@ -318,19 +304,21 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
     var alarm: GlucoseAlarm? {
         /// glucose can not be older than 20 minutes due to the predicate in the fetch request
-        guard let glucose = fetchLatestGlucose() else { return nil }
+        coredataContext.performAndWait {
+            guard let glucose = fetchLatestGlucose() else { return nil }
 
-        let glucoseValue = glucose.glucose
+            let glucoseValue = glucose.glucose
 
-        if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
-            return .low
-        }
+            if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
+                return .low
+            }
 
-        if Decimal(glucoseValue) >= settingsManager.settings.highGlucose {
-            return .high
-        }
+            if Decimal(glucoseValue) >= settingsManager.settings.highGlucose {
+                return .high
+            }
 
-        return nil
+            return nil
+        }
     }
 }
 

+ 1 - 3
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -9,8 +9,6 @@ import Swinject
 
     @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
 
-    @StateObject var dataController = CoreDataStack.shared
-
     // Dependencies Assembler
     // contain all dependencies Assemblies
     // TODO: Remove static key after update "Use Dependencies" logic
@@ -62,7 +60,7 @@ import Swinject
     var body: some Scene {
         WindowGroup {
             Main.RootView(resolver: resolver)
-                .environment(\.managedObjectContext, dataController.viewContext)
+                .environment(\.managedObjectContext, CoreDataStack.shared.viewContext)
                 .environmentObject(Icons())
                 .onOpenURL(perform: handleURL)
         }

+ 2 - 2
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -229,7 +229,7 @@ extension Home {
             }
         }
 
-        ///wait for the fetch to complete and then update the UI on the main thread
+        /// wait for the fetch to complete and then update the UI on the main thread
         private func updateGlucose() {
             Task {
                 let results = await fetchGlucoseInBackground()
@@ -239,7 +239,7 @@ extension Home {
             }
         }
 
-        ///do the heavy fetch operation in the background
+        /// do the heavy fetch operation in the background
         private func fetchGlucoseInBackground() async -> [GlucoseStored] {
             await withCheckedContinuation { continuation in
                 backgroundQueue.async {

+ 26 - 14
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -230,25 +230,37 @@ final class BaseCalendarManager: CalendarManager, Injectable {
     }
 
     private func fetchAndProcessGlucose() -> [GlucoseStored]? {
-        do {
-            debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
-            return try coredataContext.fetch(GlucoseStored.fetch(
-                NSPredicate.predicateFor20MinAgo,
-                ascending: false,
-                fetchLimit: 3
-            ))
-        } catch {
-            debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
-            return []
+        var result: [GlucoseStored]?
+        coredataContext.perform {
+            do {
+                debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+                result = try self.coredataContext.fetch(GlucoseStored.fetch(
+                    NSPredicate.predicateFor20MinAgo,
+                    ascending: false,
+                    fetchLimit: 3
+                ))
+            } catch {
+                debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            }
         }
+        return result
     }
 
     func setupGlucose() {
-        guard let glucose = fetchAndProcessGlucose(), let lastGlucose = glucose.first, let lastReading = glucose.first?.glucose,
-              let secondLastReading = glucose.dropFirst().first?.glucose else { return }
+        guard let glucose = fetchAndProcessGlucose(), glucose.count >= 2 else {
+            debugPrint("Not enough glucose data available")
+            return
+        }
 
-        let glucoseDelta = lastReading - secondLastReading
-        createEvent(for: lastGlucose, delta: Int(glucoseDelta))
+        // Safely unwrapping glucose readings
+        if let lastGlucose = glucose.first,
+           let secondLastReading = glucose.dropFirst().first?.glucose
+        {
+            let glucoseDelta = lastGlucose.glucose - secondLastReading
+            createEvent(for: lastGlucose, delta: Int(glucoseDelta))
+        } else {
+            debugPrint("Failed to unwrap necessary glucose readings")
+        }
     }
 }
 

+ 104 - 99
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -1,3 +1,4 @@
+import CoreData
 import Foundation
 import Swinject
 import WatchConnectivity
@@ -52,40 +53,80 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
         configureState()
     }
 
-    private func fetchGlucose() -> [GlucoseStored] {
-        do {
-            let fetchedReadings = try context.fetch(GlucoseStored.fetch(NSPredicate.predicateFor120MinAgo, ascending: true))
-            debugPrint("Watch Manager: \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) fetched glucose")
-            return fetchedReadings
-        } catch {
-            debugPrint("Watch Manager: \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) failed to fetch glucose")
-            return []
-        }
+    private func fetchLastDeterminationDate() -> Date? {
+        let predicate = NSPredicate.enactedDetermination
+        return CoreDataStack.shared.fetchEntities(
+            ofType: OrefDetermination.self,
+            predicate: predicate,
+            key: "deliverAt",
+            ascending: false,
+            fetchLimit: 1,
+            batchSize: 1,
+            propertiesToFetch: ["deliverAt"]
+        ).first?.deliverAt
     }
 
-    private func fetchDetermination() -> [OrefDetermination] {
-        do {
-            let results = try context.fetch(OrefDetermination.fetch(NSPredicate.enactedDetermination))
-            debugPrint("Watch Manager: \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) fetched determinations")
-            return results
-        } catch {
-            debugPrint("Watch Manager: \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) failed to fetch determinations")
-            return []
+    func fetchAndProcessGlucose() -> (ids: [NSManagedObjectID], glucose: String, trend: String, delta: String, date: Date) {
+        var results: (ids: [NSManagedObjectID], glucose: String, trend: String, delta: String, date: Date) = (
+            [],
+            "--",
+            "--",
+            "--",
+            Date()
+        )
+
+        let context = CoreDataStack.shared.backgroundContext
+        context.performAndWait {
+            let predicate = NSPredicate.predicateFor120MinAgo
+            let fetchedGlucose = CoreDataStack.shared.fetchEntities(
+                ofType: GlucoseStored.self,
+                predicate: predicate,
+                key: "date",
+                ascending: false,
+                fetchLimit: 24,
+                batchSize: 12
+            )
+
+            let ids = fetchedGlucose.map(\.objectID)
+            guard let firstGlucose = fetchedGlucose.first else {
+                return
+            }
+
+            let glucoseValue = firstGlucose.glucose
+            let date = firstGlucose.date ?? .distantPast
+            let delta = fetchedGlucose.count >= 2 ? glucoseValue - fetchedGlucose[1].glucose : 0
+
+            let units = settingsManager.settings.units
+            let glucoseFormatter = NumberFormatter()
+            glucoseFormatter.numberStyle = .decimal
+            glucoseFormatter.maximumFractionDigits = (units == .mmolL) ? 1 : 0
+
+            let glucoseText = glucoseFormatter
+                .string(from: Double(units == .mmolL ? Decimal(glucoseValue).asMmolL : Decimal(glucoseValue)) as NSNumber) ?? "--"
+
+            let directionText = firstGlucose.direction ?? "↔︎"
+
+            let deltaFormatter = NumberFormatter()
+            deltaFormatter.numberStyle = .decimal
+            deltaFormatter.maximumFractionDigits = 1
+            let deltaText = deltaFormatter
+                .string(from: Double(units == .mmolL ? Decimal(delta).asMmolL : Decimal(delta)) as NSNumber) ?? "--"
+
+            results = (ids, glucoseText, directionText, deltaText, date)
         }
+        return results
     }
 
     private func configureState() {
         processQueue.async {
-            let fetchedReadings = self.fetchGlucose()
-            let glucoseValues = self.glucoseText(fetchedReadings)
-            let fetchedDeterminations = self.fetchDetermination()
+            let glucoseValues = self.fetchAndProcessGlucose()
 
             self.state.glucose = glucoseValues.glucose
             self.state.trend = glucoseValues.trend
             self.state.delta = glucoseValues.delta
-            self.state.trendRaw = fetchedReadings.first?.direction ?? "↔︎"
-            self.state.glucoseDate = fetchedReadings.first?.date ?? .distantPast
-            self.state.lastLoopDate = fetchedDeterminations.first?.deliverAt
+            self.state.trendRaw = glucoseValues.trend
+            self.state.glucoseDate = glucoseValues.date
+            self.state.lastLoopDate = self.fetchLastDeterminationDate()
             self.state.lastLoopDateInterval = self.state.lastLoopDate.map {
                 guard $0.timeIntervalSince1970 > 0 else { return 0 }
                 return UInt64($0.timeIntervalSince1970)
@@ -112,7 +153,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
                         0
                     ))
             } else {
-                let recommended = self.newBolusCalc(delta: fetchedReadings, suggestion: self.suggestion)
+                let recommended = self.newBolusCalc(ids: glucoseValues.ids, suggestion: self.suggestion)
                 self.state.bolusRecommended = self.apsManager
                     .roundBolus(amount: max(recommended, 0))
             }
@@ -173,31 +214,6 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
         }
     }
 
-    private func glucoseText(_ glucose: [GlucoseStored]) -> (glucose: String, trend: String, delta: String) {
-        let glucoseValue = glucose.first?.glucose ?? 0
-
-        guard !glucose.isEmpty else { return ("--", "--", "--") }
-
-        let delta = glucose.count >= 2 ? glucoseValue - glucose[1].glucose : nil
-
-        let units = settingsManager.settings.units
-        let glucoseText = glucoseFormatter
-            .string(from: Double(
-                units == .mmolL ? Decimal(glucoseValue).asMmolL : Decimal(glucoseValue)
-            ) as NSNumber)!
-
-        let directionText = glucose.first?.direction ?? "↔︎"
-        let deltaText = delta
-            .map {
-                self.deltaFormatter
-                    .string(from: Double(
-                        units == .mmolL ? Decimal($0).asMmolL : Decimal($0)
-                    ) as NSNumber)!
-            } ?? "--"
-
-        return (glucoseText, directionText, deltaText)
-    }
-
     private func descriptionForTarget(_ target: TempTarget) -> String {
         let units = settingsManager.settings.units
 
@@ -225,60 +241,49 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
         )!
     }
 
-    private func newBolusCalc(delta: [GlucoseStored], suggestion _: Suggestion?) -> Decimal {
-        var conversion: Decimal = 1
-        // Settings
-        if settingsManager.settings.units == .mmolL {
-            conversion = 0.0555
-        }
-        let isf = state.isf ?? 0
-        let target = suggestion?.current_target ?? 0
-        let carbratio = suggestion?.carbRatio ?? 0
-        let bg = delta.first?.glucose ?? 0
-        let cob = state.cob ?? 0
-        let iob = state.iob ?? 0
-        let useFattyMealCorrectionFactor = settingsManager.settings.fattyMeals
-        let fattyMealFactor = settingsManager.settings.fattyMealFactor
-        let maxBolus = settingsManager.pumpSettings.maxBolus
+    private func newBolusCalc(ids: [NSManagedObjectID], suggestion: Suggestion?) -> Decimal {
         var insulinCalculated: Decimal = 0
-        // insulin needed for the current blood glucose
-        let targetDifference = (Decimal(bg) - target) * conversion
-        let targetDifferenceInsulin = targetDifference / isf
-        // more or less insulin because of bg trend in the last 15 minutes
-        var bgDelta: Int = 0
-        if delta.count >= 3 {
-            bgDelta = Int((delta.first?.glucose ?? 0) - delta[2].glucose)
-        }
-        let fifteenMinInsulin = (Decimal(bgDelta) * conversion) / isf
-        // determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
-        let wholeCobInsulin = cob / carbratio
-        // determine how much the calculator reduces/ increases the bolus because of IOB
-        let iobInsulinReduction = (-1) * iob
-        // adding everything together
-        // add a calc for the case that no fifteenMinInsulin is available
-        var wholeCalc: Decimal = 0
-        if bgDelta != 0 {
-            wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin + fifteenMinInsulin)
-        } else {
-            // add (rare) case that no glucose value is available -> maybe display warning?
-            // if no bg is available, ?? sets its value to 0
-            if bg == 0 {
-                wholeCalc = (iobInsulinReduction + wholeCobInsulin)
+
+        context.performAndWait {
+            let glucoseObjects = ids.compactMap { self.context.object(with: $0) as? GlucoseStored }
+            guard let firstGlucose = glucoseObjects.first else {
+                return // If there's no glucose data, exit the block
+            }
+            let bg = firstGlucose.glucose // Make sure to provide a fallback value for glucose
+
+            // Calculations related to glucose data
+            var bgDelta: Int = 0
+            if glucoseObjects.count >= 3 {
+                bgDelta = Int(firstGlucose.glucose) - Int(glucoseObjects[2].glucose)
+            }
+
+            let conversion: Decimal = settingsManager.settings.units == .mmolL ? 0.0555 : 1
+            let isf = state.isf ?? 0
+            let target = suggestion?.current_target ?? 0
+            let carbratio = suggestion?.carbRatio ?? 0
+            let cob = state.cob ?? 0
+            let iob = state.iob ?? 0
+            let fattyMealFactor = settingsManager.settings.fattyMealFactor
+
+            // Complete bolus calculation logic
+            let targetDifference = Decimal(bg) - target
+            let targetDifferenceInsulin = targetDifference * conversion / isf
+            let fifteenMinInsulin = Decimal(bgDelta) * conversion / isf
+            let wholeCobInsulin = cob / carbratio
+            let iobInsulinReduction = -iob
+            let wholeCalc = targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin + fifteenMinInsulin
+
+            let result = wholeCalc * settingsManager.settings.overrideFactor
+            if settingsManager.settings.fattyMeals {
+                insulinCalculated = result * fattyMealFactor
             } else {
-                wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin)
+                insulinCalculated = result
             }
         }
-        // apply custom factor at the end of the calculations
-        let result = wholeCalc * settingsManager.settings.overrideFactor
-        // apply custom factor if fatty meal toggle in bolus calc config settings is on and the box for fatty meals is checked (in RootView)
-        if useFattyMealCorrectionFactor {
-            insulinCalculated = result * fattyMealFactor
-        } else {
-            insulinCalculated = result
-        }
-        // Not 0 or over maxBolus
-        insulinCalculated = max(min(insulinCalculated, maxBolus), 0)
-        return insulinCalculated
+
+        // Ensure the calculated insulin amount does not exceed the maximum bolus and is not below zero
+        insulinCalculated = max(min(insulinCalculated, settingsManager.pumpSettings.maxBolus), 0)
+        return insulinCalculated // Return the calculated insulin outside of the performAndWait block
     }
 
     private var glucoseFormatter: NumberFormatter {

+ 43 - 0
Model/CoreDataStack.swift

@@ -33,4 +33,47 @@ class CoreDataStack: ObservableObject {
         viewContext.automaticallyMergesChangesFromParent = true
         return viewContext
     }()
+
+    func fetchEntities<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
+    ) -> [T] {
+        let request = NSFetchRequest<T>(entityName: String(describing: type))
+        request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
+        request.predicate = predicate
+        if let limit = fetchLimit {
+            request.fetchLimit = limit
+        }
+        if let batchSize = batchSize {
+            request.fetchBatchSize = batchSize
+        }
+        if let propertiesTofetch = propertiesToFetch {
+            request.propertiesToFetch = propertiesTofetch
+            request.resultType = .dictionaryResultType
+        } else {
+            request.resultType = .managedObjectResultType
+        }
+
+        var result: [T]?
+
+        /// we need to ensure that the fetch immediately returns a value as long as the whole app does not use the async await pattern, otherwise we could perform this asynchronously with backgroundContext.perform and not block the thread
+        backgroundContext.performAndWait {
+            do {
+                debugPrint("Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded)")
+                result = try self.backgroundContext.fetch(request)
+            } catch {
+                debugPrint(
+                    "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error)"
+                )
+            }
+        }
+        return result ?? []
+    }
 }

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

@@ -42,10 +42,6 @@ extension NSPredicate {
     }
 }
 
-protocol GlucoseStoredObserver {
-    func glucoseDidUpdate(_ glucose: [GlucoseStored])
-}
-
 extension GlucoseStored: Encodable {
     enum CodingKeys: String, CodingKey {
         case date