iOS Intermittent Bug: UserDefaults Preferences Loading Issue
Problem Summary
We're experiencing an intermittent issue where UserPreferences.shared.preferences returns inconsistent values even after calling getPreferences(). The behavior is unpredictable and affects critical functionality.
Environment
- iOS Version: 15+
- Language: Objective-C with Swift interop
- Storage: UserDefaults with App Group (
group.com.jci.tyco.glss) - Architecture: Singleton pattern for UserPreferences (Swift class)
The Issue
When a push notification arrives and triggers the showEvent: method, user preferences are sometimes loaded correctly and sometimes return nil or default values:
Scenario A (Works - ~60% of time):
Scenario B (Fails - ~40% of time):
Observed Pattern
From extensive logging over multiple test runs:
Key Observation:
- At app launch: Preferences often load successfully
- Seconds later when push arrives: Same preferences become unavailable
- User navigates to another screen and back: Preferences suddenly work again
Code Structure
Objective-C AppDelegate.m
- (void)showEvent:(FMEvent *)event eventReadSource:(FMEventReadSourceType)eventSource {
BOOL isUserRoleEndUser = [[FMUserRolesRepository instance] userRoleAvailable:FMUserRoleEndUser];
UserPreferences *userPrefs = [UserPreferences shared];
[userPrefs getPreferences]; // ← Called here
// Immediately after, sometimes nil!
bool hasFireAudio = [userPrefs.preferences.fireAudio.lowercaseString isEqual:@"true"];
NSLog(@"isUserRoleEndUser: %d | Fire Audio: %d", isUserRoleEndUser, hasFireAudio);
// Decision logic based on preferences
if ([FMConstants getUserRegion] == UserRegionUK &&
isUserRoleEndUser &&
userPrefs.preferences && // ← Sometimes nil here
hasFireAudio) {
// Show Screen A
} else {
// Show Screen B
}
}
```class UserPreferences: NSObject {
static let shared = UserPreferences()
var preferences: FMUserPreferencesInfo? // ← This becomes nil intermittently
func getPreferences() {
let userDefaults = UserDefaults(suiteName: "group.com.jci.tyco.glss")
if let data = userDefaults?.data(forKey: "UserPreferences") {
// Decode and set preferences
self.preferences = // decoded object
} else {
// Create default preferences with fireAudio = "false"
self.preferences = FMUserPreferencesInfo()
}
}
}
Looking at the code you posted, the only way [1] that preferences can be nil at line 15 is if your decoded object code fails. You didn’t show that code, so it’s hard to tell what’s going on there.
It sounds like you can reproduce this reasonably reliably. Given that, you can debug this using logging. I have general advice on that topic in Testing and Debugging Code Running in the Background.
IMPORTANT It’s time to stop using NSLog habitually. Rather, use print(…) for transient debugging and Logger for debugging that you want to keep in your codebase (or for cases like this, where you need to debug without Xcode being attached).
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] Well, not the only way. You could be dealing with an issue that’s outside of the scope of the Objective-C / Swift execution models, like a memory corruption problem. However, let’s rule out the easy things first (-: