Jaded Bucket
Jaded Bucket

Reputation: 44

Inconsistent value from NSUserDefaults at app startup

Under the iOS simulator, I'm having trouble getting a consistent value back from NSUserDefault on app startup. I've set the value in a previous run of the app (and I put some prints in there to make sure there was no accidental changes), so I don't think there's a problem with the way I'm calling synchronize(). The reads are happening on the main thread also, and after I've set the default values. After a couple of seconds, if I retry the fetch from NSUserDefaults, I get the correct value (I put a timer in there to verify that the value gets corrected and seems to stay the same after startup).

The value represents where the user will store the data (on iCloud or just on the local device) and is represented by an integer in the user defaults. Here's the enum representing the values:

class MovingDocument: UIDocument {
    enum StorageSettingsState: Int {
        case NoChoice = 0
        case Local = 1
        case ICloud = 2
    }
}

Here's an excerpt of the class used to access NSUserDefaults:

class Settings {
    static let global = Settings()

    private enum LocalStoreKeys : String {
        case IsHorizontal = "IsHorizontal"
        case ItemInPortrait = "ItemInPortrait"
        case RotateTitle = "RotateTitle"
        case StorageLocation = "StorageLocation"
    }

    // Other computed variables here for the other settings.

    // Computed variable for the storage setting.
    var storageLocation: MovingDocument.StorageSettingsState {
        get {
            print("Storage state fetch: \(NSUserDefaults.standardUserDefaults().integerForKey(LocalStoreKeys.StorageLocation.rawValue))")
            return MovingDocument.StorageSettingsState(rawValue: NSUserDefaults.standardUserDefaults().integerForKey(LocalStoreKeys.StorageLocation.rawValue)) ?? .NoChoice
        }
        set {
            print("Setting storage location to \(newValue) (\(newValue.rawValue))")
            NSUserDefaults.standardUserDefaults().setInteger(newValue.rawValue, forKey: LocalStoreKeys.StorageLocation.rawValue)
            synchronize()
            CollectionDocument.settingsChanged()
        }
    }

    func prepDefaultValues() {
        NSUserDefaults.standardUserDefaults().registerDefaults([
            LocalStoreKeys.IsHorizontal.rawValue: true,
            LocalStoreKeys.ItemInPortrait.rawValue: true,
            LocalStoreKeys.RotateTitle.rawValue: true,
            LocalStoreKeys.StorageLocation.rawValue: MovingDocument.StorageSettingsState.NoChoice.rawValue
            ])
    }

    func synchronize() {
        NSUserDefaults.standardUserDefaults().synchronize()
    }
}

The value that I should be reading (and which I always seem to get after startup) is the value '1' or .Local. The value that I am sometimes getting instead is '0' or .NoChoice (which just happens to be the default value). So, it looks like it's using the default value until some later point in the application startup.

For completeness sake, here's the relevant app delegate code:

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.

    Settings.global.prepDefaultValues()  // Must be called before doing CollectionDocument.start() because we need the default value for storage location.
    CollectionDocument.start()
    self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
    browserController = ViewController()
    navController = UINavigationController(rootViewController: browserController!)
    navController!.navigationBar.translucent = false
    self.window!.rootViewController = navController
    self.window!.makeKeyAndVisible()
    checkStorageLocation()
    return true
}

The getter is called within CollectionDocument.start() (CollectionDocument is a subclass of MovingDocument, where the location enum is defined). The call at the end to checkStorageLocation() is my periodic debugging check for the value of the storage location (it calls itself back via dispatch_after()).

As a side note, I also tried putting most of the code in the application(didFinishLaunchingWithOptions...) into a dispatch_async call (on the main thread) to see if that made a difference (just the call to set up the default values and the return statement were done immediately, the rest of the calls were put into the asynchronous call block), and it still got the wrong value (sometimes). I guess I could try using dispatch_after instead (and wait a second or two), but I can't really start the app until this is complete (because I can't read the user data from the doc until I know where it is) and a loading screen for just this one problem seems ridiculous.

Any ideas about what might be going wrong? I'm unaware of any limitations on NSUserDefaults as far as when you are allowed to read from it...

Upvotes: 0

Views: 818

Answers (1)

Jaded Bucket
Jaded Bucket

Reputation: 44

Rebooting my mac fixed the problem (whatever it was). iOS 10, NSUserDefaults Does Not Work has a similar problem and the same solution (though the circumstances are quite different, since I have tried neither Xcode 8 nor iOS 10).

Upvotes: 1

Related Questions