肇鑫的技术博客

业精于勤,荒于嬉

UserDefaults的正确用法(修正版)

通常使用UserDefaults的方法有两种。

func applicationDidFinishLaunching(_ aNotification: Notification) {
    let dic = ["test":true]
    let id = Bundle.main.bundleIdentifier!
    // 1
    UserDefaults.standard.setPersistentDomain(dic, forName: id)
    // 2
    UserDefaults.standard.register(defaults: dic)
}

我们知道,PersistentDomain是永久的,会写入到磁盘。而register是临时的,每次程序启动都需要重新加载。因此,上面的代码有一些问题,需要改为

func applicationDidFinishLaunching(_ aNotification: Notification) {
   let dic = ["test":true]
   let id = Bundle.main.bundleIdentifier!
   if let _ = UserDefaults.standard.persistentDomain(forName: id) {
       
   }
   else {
       UserDefaults.standard.setPersistentDomain(dic, forName: id)
   }
   
   UserDefaults.standard.register(defaults: dic)
}

即,必须先确定没有这个PersistentDomain,然后才能注册。当重置设置为默认时,二者的代码也有一些差异。

let dic = ["test":true]
let id = Bundle.main.bundleIdentifier!
// 1
UserDefaults.standard.removePersistentDomain(forName: id)
UserDefaults.standard.setPersistentDomain(dic, forName: id)
// 2
UserDefaults.standard.resetStandardUserDefaults()

上面代码1是大家经常在网上看到的,但是它实际存在问题,是不正确的。这段代码表面看起来似乎没什么问题,第一步是删除所有设置,第二部是将设置设为默认。但是,如果你考虑到通知的,就会明白了。假设我们有一个注册到UserDefaults.didChangeNotification的通知。那么第一步就会发出通知,第二步还会再发一次。但是,由于第一步实际上是删除了所有的设置,此时程序有极大的可能会出错。

如果要解决这个问题,上面的代码可能要改为先解除注册的通知,然后删除所有设置,然后再注册通知,再将设置改为默认。但是且慢,我们真的需要这么做吗?为什么一定要清空才能再设置呢?难道不是直接设置就可以了吗?是的,其实直接设置就可以了,完全没必要清空。这样代码也就变成了下面这样:

let dic = ["test":true]
let id = Bundle.main.bundleIdentifier!
// 1
UserDefaults.standard.setPersistentDomain(dic, forName: id)
// 2
UserDefaults.standard.resetStandardUserDefaults()

那么方法2是怎么回事?可不可以也采用直接设置的方式?比如UserDefaults.standard.register(defaults: dic)。实际上是不可以的。这涉及到UserDefaults的原理。在实际使用中,系统是将registration domain和其它domain联合使用的。苹果的文档这样写到。

The registration domain defines the set of default values to use if a given preference is not set explicitly in one of the other domains.
如果其它domain没有设置某个设置,就使用registration domain定义的设置,即它实际是fail safe的默认值。

实际上registration domain,是临时的设置,你在每次启动程序时,都需要设置它。但是,如果用户将registration domain中含有的项的值改变了,系统就会自动将改变的内容写入到一个以你的程序命名的plist中。这里的值的优先级别,要比registration domain里的值的级别高,程序会以这里的为准。当然,你也可以通过直接写入PersistentDomain方法来修改这里的值。但是我们一般不会这么做。而是使用UserDefaults的一系列set方法,当使用set方法时,系统会自动将设置写入到plist中。

使用registration domain的好处

  1. 用户仅能见到非默认的设置,如果有些设置你不打算让用户知道或修改,这样更安全。
  2. 注册和删除的方式更加优雅。
  3. 添加新设置时更为方便,无需考虑程序版本。

结论

正确的使用UserDefaults的方法是:

  1. 在程序每次启动时调用registration domain来写如程序的默认值
  2. 当默认值被被用户修改时,调用UserDefaults的系列set方法来调用
  3. 除非要写入到其它的plist,我们一般不必使用PersistentDomain