From a88521c35fe000df78023b43d29666cfed2c3183 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:47:12 -0400 Subject: [PATCH] Prevented a couple potential crashes Not force unwrapping things is good! --- TigerDine.xcodeproj/project.pbxproj | 16 +++++------ TigerDine/Data/DiningModel.swift | 42 +++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/TigerDine.xcodeproj/project.pbxproj b/TigerDine.xcodeproj/project.pbxproj index 8e3bbdd..68fc5c0 100644 --- a/TigerDine.xcodeproj/project.pbxproj +++ b/TigerDine.xcodeproj/project.pbxproj @@ -292,7 +292,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 34; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = 5GF7GKNTK4; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TigerDineWidgets/Info.plist; @@ -304,7 +304,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.2.1; + MARKETING_VERSION = 1.2.2; PRODUCT_BUNDLE_IDENTIFIER = "dev.ninjacheetah.RIT-Dining.Widgets"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -325,7 +325,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 34; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = 5GF7GKNTK4; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TigerDineWidgets/Info.plist; @@ -337,7 +337,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.2.1; + MARKETING_VERSION = 1.2.2; PRODUCT_BUNDLE_IDENTIFIER = "dev.ninjacheetah.RIT-Dining.Widgets"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -481,7 +481,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 34; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = 5GF7GKNTK4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -500,7 +500,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.1; + MARKETING_VERSION = 1.2.2; PRODUCT_BUNDLE_IDENTIFIER = "dev.ninjacheetah.RIT-Dining"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -518,7 +518,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 34; + CURRENT_PROJECT_VERSION = 35; DEVELOPMENT_TEAM = 5GF7GKNTK4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -537,7 +537,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.1; + MARKETING_VERSION = 1.2.2; PRODUCT_BUNDLE_IDENTIFIER = "dev.ninjacheetah.RIT-Dining"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/TigerDine/Data/DiningModel.swift b/TigerDine/Data/DiningModel.swift index ab8f9cb..dc1683a 100644 --- a/TigerDine/Data/DiningModel.swift +++ b/TigerDine/Data/DiningModel.swift @@ -94,20 +94,33 @@ class DiningModel { // If we can't access the lastRefreshed key, then there is likely no cache. if let lastRefreshed = lastRefreshed { if Calendar.current.startOfDay(for: now) == Calendar.current.startOfDay(for: lastRefreshed) { - print("cache hit, loading from cache") // Last refresh happened today, so the cache is fresh and we should load that. + print("cache hit, trying load from cache") await getDaysRepresented() let decoder = JSONDecoder() - let cachedLocationsByDay = try decoder.decode([[DiningLocation]].self, from: (UserDefaults(suiteName: "group.dev.ninjacheetah.RIT-Dining")!.data(forKey: "cachedLocationsByDay")!)) - // Load cache, update open status, do a notification cleanup, and return. We only need to clean up because loading - // cache means that there can't be any new notifications to schedule since the last real data refresh. - locationsByDay = cachedLocationsByDay - updateOpenStatuses() - await cleanupPushes() - - isLoaded = true - return + // These checks ensure that the key can actually be loaded from UserDefaults and that the cached JSON data can + // actually be loaded from the cache before trying to use it, to prevent potential crashes from force unwrapping + // it. Currently unclear on what could make these fail if the lastRefreshed date loaded as today, but this should + // mitigate it by falling back on a network load if they do. + if let cacheUserDefaults = UserDefaults(suiteName: "group.dev.ninjacheetah.RIT-Dining") { + if let cacheData = cacheUserDefaults.data(forKey: "cachedLocationsByDay") { + let cachedLocationsByDay = try decoder.decode([[DiningLocation]].self, from: cacheData) + + // Load cache, update open status, do a notification cleanup, and return. We only need to clean up because + // loading cache means that there can't be any new notifications to schedule since the last real data refresh. + locationsByDay = cachedLocationsByDay + updateOpenStatuses() + await cleanupPushes() + + isLoaded = true + return + } else { + print("cache exists, but failed to load JSON data") + } + } else { + print("cache appears to exist, but failed to load from UserDefaults") + } } print("cache miss") // Otherwise, the cache is stale and we can fall out to the call to update it. @@ -153,7 +166,14 @@ class DiningModel { let now = Date() for push in visitingChefPushes.pushes { if now > push.endTime { - visitingChefPushes.pushes.remove(at: visitingChefPushes.pushes.firstIndex(of: push)!) + // Guard this with an if let to avoid force unwrapping the index. That's something that theoretically + // should always be safe given that this is iterating over elements so obviously that element should exist, + // however there was an issue where this would sometimes unwrap a nil. My theory is that there was a small + // chance of this task getting run twice concurrently under certain conditions, and so one would remove the + // notification right before the other tried, and then it would be gone and the index would be nil. + if let pushIndex = visitingChefPushes.pushes.firstIndex(of: push) { + visitingChefPushes.pushes.remove(at: pushIndex) + } } } }