Persisting Data in NSUserDefaults January 3, 2017

Saving to NSUserDefaults is a simple way to persist data within your apps. The first method you'll need is a save() function that encodes an object, and then sets it as a value in UserDefaults.

            
      func save() {
          let encodedData = NSKeyedArchiver.archivedData(withRootObject: collections)
          UserDefaults.standard.setValue(encodedData, forKey: "collections")
      }
      
          

The other part of saving an object is using NSCoding to tell the class how to encode itself. This is done with a set of inits inside the class.

            
      import UIKit

      class Collection: NSObject, NSCoding {
          
          var name: String?
          var isPremium: Bool?
          var phrases: [Phrase]?
          
          required init(coder decoder: NSCoder) {
              self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
              self.isPremium = decoder.decodeBool(forKey: "isPremium")
              self.phrases = decoder.decodeObject(forKey: "phrases") as? [Phrase]
              super.init()
          }
          
          func encode(with aCoder: NSCoder) {
              aCoder.encode(name, forKey: "name")
              aCoder.encode(isPremium, forKey: "isPremium")
              aCoder.encode(phrases, forKey: "phrases")
          }
          
          override init() {
              super.init()
          }
      }
      
          

We are saving an object that contains a custom class object, so both need to be encoded/decoded. One stores a collection of phrases in Spanish and English, and the other contains many of those collections... they are nested objects, and both must use NSCoding.

            
      class Phrase: NSObject, NSCoding {
          
          var spanish: String = String()
          var english: String = String()
          
          required init(coder decoder: NSCoder) {
              self.english = decoder.decodeObject(forKey: "english") as? String ?? ""
              self.spanish = decoder.decodeObject(forKey: "english") as? String ?? ""
              super.init()
          }
          
          func encode(with aCoder: NSCoder) {
              aCoder.encode(english, forKey: "english")
              aCoder.encode(spanish, forKey: "spanish")
          }
          
          init(english: String, spanish: String) {
              super.init()
              self.english = english
              self.spanish = spanish
          }
      }
      
          

I've included the protocol NSCoding in the class declaration, and then added three inits:

  1. required init(coder decoder: NSCoder) is for decoding the class when loading from NSUserDefaults
  2. func encode(with aCoder: NSCoder) encodes, preparing objects and variables to be stored in NSUserDefaults
  3. override init() must be included as well

Then call save() at some appropriate point in the App's lifecycle, like applicationDidEnterBackground...

            
      let dataController = DataController()

      func applicationDidEnterBackground(_ application: UIApplication) {
          dataController.save()
      }
      
          

In order to load the data back from NSUserDefaults call this load() method:

            
      func load() {
          if let data = UserDefaults.standard.data(forKey: "collections") {
              collections = (NSKeyedUnarchiver.unarchiveObject(with: data) as? [Collection])!
              print("Loaded from NSUserDefaults")
          } else {
              print("Unable to load from NSUserDefaults")
          }
      }
        
          

The required decoder init already exists in the class (we did that earlier), so this simple call will retrieve the objects from NSUserDefaults.

That's it! Have fun and #CompileSwift.