Coding Explorer Blog

Exploring how to code for iOS in Swift and Objective-C

  • Home
  • Apps
  • About
  • Contact

NSUserDefaults — A Swift Introduction

Xcode 11.6 Swift 5.2.4

Last updated on August 12, 2020

So, let’s say we have an app that needs to remember a few simple things that the user puts in when they first load the app.  It needs to remember the user’s name and birthday, to show on some view controller, or maybe even for a countdown on their Apple Watch.

There are plenty of ways to save data for your app.  Some are easy to use, but rather limited, while others are much harder to use, but give you a lot more capabilities.  Today, we are going to cover something on the easy, but limited end of the spectrum.  For the app mentioned above, the information we’re storing will be used to set this app up with default values, for this user anyway.

That is why this method is called NSUserDefaults.  It has its limitations, but it is very easy to use, and is ideal for simple storage of things like Strings and numbers.

Storable Types in NSUserDefaults

The NSUserDefaults class acts very much like something called a Property List (aka plist).  It may be just a fancy interface for a plist, or it may be more, I’m not entirely sure.  Nonetheless, plists are limited in what kind of objects they can store.  The six types plists can store are:

  • NSData
  • NSString
  • NSNumber
  • NSDate
  • NSArray
  • NSDictionary

Just to make it clear, since they only differ by one letter, the first one listed as NSData, while the fourth one is an NSDate.  A property list, or NSUserDefaults can store any type of object that can be converted to an NSData object.  It would require any custom class to implement that capability, but if it does, that can be stored as an NSData.  These are the only types that can be stored directly.  Thankfully, Swift Strings, Arrays, and Dictionaries are automatically converted to their NS counterparts, so they can be stored in here as well.

An NSNumber is an NSObject that can contain the original C style numeric types, which even include the C/Objective-C style BOOL type (that stores YES or NO).  Thankfully for us, these are also bridged to Swift, so for a Swift program, an NSNumber can automatically accept the following Swift types:

  • UInt
  • Int
  • Float
  • Double
  • Bool

That final one is that Swift style Bool that stores the value of “true” or “false”.  Nonetheless, if we want to expand this out slightly to show The Swift versions instead of their Objective-C forms, where applicable, you can store the types:

  • Data
  • String
  • NSNumber
    • UInt
    • Int
    • Float
    • Double
    • Bool
  • Date
  • Array
  • Dictionary

Writing to NSUserDefaults

First, to read from or write to NSUserDefaults, you have to get a reference to it.  In the simplest use of NSUserDefaults, this is done via a method that returns a reference to an object capable of interacting with NSUserDefaults’s data store.  This method returns something very much like a singleton.  A singleton is an object that only has one instance of it generated per program.  When you call the class method the first time, the object is instantiated and a reference is passed back.  When you call that method again, it does not instantiate a new one, and passes the aforementioned reference again, making both point to the same instance.  Singletons are often viewed as an anti-pattern, particularly since they are difficult to test with.  They are often used to give global state to apps, which hide dependencies within the single reference to a large object, instead of having them explicitly injected through a type’s public interface or protocol.

Nonetheless, we are not covering singletons in general today, but just be aware that while this is effectively a singleton, you should be careful about using the design pattern yourself.  When I use NSUserDefaults, I have had all of its accesses tucked away in a single class, that if I want to test, I can just replace that single class with another one that adopts the same protocol that has the values I want to test with (and thus side-step using NSUserDefaults altogether in the test, I’m not supposed to be testing NSUserDefaults itself).

Anyway, all that to say that we basically need to get a reference to NSUserDefaults, and then ask it to save something.  Below is sample code of how to do that:

let defaults = UserDefaults.standard
defaults.set("Coding Explorer", forKey: "userNameKey")

So, the first line is getting a reference to something that can access NSUserDefaults with the standard class property.  After that, we tell it to set an object with a specific key.  In this case, we are storing the user name, which I put in as “Coding Explorer”, and then give that a key to use to recall it later, similar to using a Dictionary.

There are several convenience methods for storing data in NSUserDefaults they include:

  • func set(_ value: Bool, forKey defaultName: String)
  • func set(_ value: Int, forKey defaultName: String)
  • func set(_ value: Float, forKey defaultName: String)
  • func set(_ value: Double, forKey defaultName: String)
  • func set(_ value: Any?, forKey defaultName: String)
  • func set(_ url: URL?, forKey defaultName: String)

Reading from NSUserDefaults

Reading is done in a very similar fashion.  You need to get a reference to the NSUserDefaults object, and then ask it for the value you want.  In the case of reading out the String we wrote in and printing it to the console, we would use the code:

let defaults = UserDefaults.standard
if let name = defaults.string(forKey: "userNameKey") {
    print(name)
}

The methods to read something out, similar to a dictionary, return an Optional of whatever you are looking for, if it can. That way, if something does NOT exist for the key you used, it would return nil.  So, in this example, we optionally bind the output of stringForKey(“userNameKey”) to the constant “name”.  If it exists at that key, then we store it in the name constant, and go into the if statement and proceed with the code in there (to print the “name” String).  Otherwise, if a value is not found for that key, that if statement results in a false and thus we don’t try to access the value since it is actually nil.

Much like writing to NSUserDefaults, there are several convenience methods for reading data back out, that are a bit more full featured than writing them:

  • func boolForKey(defaultName: String) -> Bool
  • func integerForKey(defaultName: String) -> Int
  • func floatForKey(defaultName: String) -> Float
  • func doubleForKey(defaultName: String) -> Double
  • func objectForKey(defaultName: String) -> AnyObject?
  • func URLForKey(defaultName: String) -> NSURL?
  • func dataForKey(defaultName: String) -> NSData?
  • func stringForKey(defaultName: String) -> String?
  • func stringArrayForKey(defaultName: String) -> [String]?
  • func arrayForKey(defaultName: String) -> [AnyObject]?
  • func dictionaryForKey(defaultName: String) -> [String : AnyObject]?

Since this is an Objective-C class there are some caveats to how this works, that makes it a bit odd for Swift.  First, you will notice that the ones that read Bool, Int, Float, and Double do not return Optionals.  In Objective-C, this returns the C primitive types for those variables.  As such, they could not be set to nil, since they weren’t Objective-C objects.  In those cases, they return sentinel values appropriate to their types which are false (or NO), 0, 0.0, and 0.0 respectively.  Maybe someday this class will be modified to have them return Optionals in Swift eventually, but for now, this is how they work.

Next, in Objective-C, NSArrays and NSDictionaries could store any type of object in them.  You could have an NSArray holding types like  NSInteger, NSViewController, UIImage, or UIActivityViewController all in the same NSArray.  As such, the types coming out of NSArrays or NSDictionaries were of the Objective-C type “id”, which translates to “ANYObject” in Swift.  That’s why stringArrayForKey, arrayForKey, and dictionaryForKey return with AnyObject in their return types.  In the case of objectForKey, that is exactly what you’re asking for, so that one makes sense.

In the case of storing an NSDate, you will have to store it as an NSObject, and type-cast it back to an NSDate when you read it back out.  For any other type of value not included in the major plist storage types, you will have to encode it into an NSData, write that with setObject:ForKey:, and read it back with dataForKey:.  I have not learned much about NSCoding yet, but that is what you would use for archiving custom classes to NSData.

Use Constants for your Keys

Now, for simplicity, I wrote the Swift String literal each time I wrote the key.  I didn’t want to set up what I’m about to say beforehand just to make the code to access a value from NSUserDefaults as easy to understand as possible (to know for sure there is a String there).

Doing it THAT way in production code is a very bad idea.

Instead, you should create a constant for that value, and assign the Swift String Literal to it.  This helps for a few reasons:

  1. If you need to change the literal for some reason, you only have to change it in one place.
  2. Xcode can now help autocomplete the constant’s name in your method call.
  3. If you make a typo with the constant name, you get a compile-time error (while a typo in a String literal is a run-time error).

So, here is some example code that has 2 buttons running the previous code, but with a constant defined:

let userNameKeyConstant = "userNameKey"

@IBAction func writeButtonWasTapped(_ sender: UIButton) {
    let defaults = UserDefaults.standard
    defaults.set("Coding Explorer", forKey: "userNameKey")
}

@IBAction func readButtonWasTapped(_ sender: UIButton) {
    let defaults = UserDefaults.standard

    if let name = defaults.string(forKey: userNameKeyConstant) {
        print(name)
    }
}

I actually tend to have the value stored in the key constant be the same as the variable name, but I wanted to show here clearly that you don’t have to (the constant is named “userNameKeyConstant” while the actual String stored within is “userNameKey”).  As you can see, the code is pretty much the same, just with that new constant instead of the String literal.  I defined the constant at the class scope (which means within the class’s curly braces, the class declaration was not shown in this example), and used it in the individual methods.

Conclusion

NSUserDefaults is ideal for storing small bits of information between app launches.  Its API is very simple, and does its job well.  It does show its Objective-C roots in Swift moreso than some other classes, but it still gets the job done.

It is best when used for little information like user preferences, but not for larger things like UIImages.  If want to store more complex things, it is better to use some of the more full-featured forms of data persistence.  You could work directly with a plist yourself, write the file to disk, or even use Core Data.  We can cover these later, but I figured starting with the simplest form would be best.

NSUserDefaults is in general read from, and written to, atomically.  This is good for making sure the data is read and written correctly, but as such it is ALL written when something changes.  So if you have something in there, and you change something small like a bool, the whole thing needs to be written again, so even though only a small part has changed, you’re still writing back out that large part.  Same thing for reading.  If you only needed to read that boolean, when NSUserDefaults first loads, it will still read everything, and then just give you the bool you requested.  It is cached after the initial load, so it shouldn’t be too slow afterwards, but the initial read could be quite slow with larger amounts of data.

There is another reason for looking into NSUserDefaults.  Using NSUserDefaults in combination with App Groups is the simplest way to share data between your app and any extensions it has.  We have not covered that in this post, it is already over 2,000 words long at this point, so we’ll cover that in a more specialized post.  Extensions should load quickly, so you still don’t want to put much in there, but preferences like Strings are easily shared between apps and their extensions using this method.

I hope you found this article helpful.  If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps.  Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do.  Thanks!

Sources

  • The Swift Programming Language – Apple Inc.
  • Using Swift with Cocoa and Objective-C – Apple Inc.
  • NSUserDefaults Class Reference — Apple Inc.
  • NSNumber Class Reference — Apple Inc.
  • Mac OS X Developer Release Note — NSUserDefaults — Apple Inc.

Filed Under: Tutorial Tagged With: optionals, Swift

Subscribe to the Coding Explorer Newsletter

* indicates required

Follow Us

Facebooktwitterrss

Recent Posts

  • Error Handling in Swift
  • Creating and Modifying a URL in your Swift App
  • Watch Connectivity in Swift — Application Context
  • watchOS Hello World App in Swift
  • API Availability Checking in Swift

Categories

  • Class Reference
  • General
  • Interface Builder
  • My Apps
  • Objective-C
  • Swift
  • Syntax
  • Tutorial
  • Uncategorized

Archives

  • May 2016
  • March 2016
  • February 2016
  • December 2015
  • July 2015
  • June 2015
  • April 2015
  • February 2015
  • January 2015
  • December 2014
  • November 2014
  • October 2014
  • September 2014
  • August 2014
  • July 2014
  • June 2014
  • May 2014
  • April 2014
  • March 2014
  • January 2014
  • November 2013
  • September 2013
  • August 2013
  • July 2013
  • Terms Of Use
  • Privacy Policy
  • Affiliate Disclaimer

Subscribe to the Coding Explorer Newsletter

* indicates required

Copyright © 2025 Wayne Media LLC · Powered by Genesis Framework · WordPress