最近在项目中用到NSUserDefaults来进行数据持久化存储,以前只是对它有个概念上的理解,但对其原理了解并不够深刻,正好最近用到来,所以想把它好好弄清楚
NSUserDefaults常用场景
我们知道NSUserDefaults是一种轻量级的iOS本地数据存储方式,通常用来存放一些app的一些偏好设置相关信息,比如像用户的用户名,密码,字体大小等设置,例如:
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isLogin"];
然后在需要用到的地方取出来
[[NSUserDefaults standardUserDefaults] boolForKey:@"isLogin"];
相信对于 NSUserDefaults 的这种使用方式大家已经非常熟悉了。 但有一些细节还是值得我们仔细斟酌一番。 我们上面的代码使用 boolForKey 方法来取得 isLogin 的值。 这个调用看起来没有任何问题,除了一种情况之外。那就是,如果在获取 isLogin 的值之前它并没有被设置,应该返回什么呢? 如果使用 boolForKey,对于不存在的 key,返回的是 false。
这是一个默认值,但并不一定符合你的业务逻辑。比如你需要对 isLogin 真正为 false 的时候进行某些处理, 但 boolForKey 会对不存在的 key 也返回 false。 这种情况下,它就会扰乱你的业务逻辑。因此我们可以用下面这种方式来解决:
[[NSUserDefaults standardUserDefaults] objectForKey:@"isLogin"];
因为objectForKey方法对于不存在的key值会返回nil,而不是像之前的boolForKey返回false。
同样的问题还存在于其他字段类型, 比如 integerForKey 方法。 默认情况下,如果所访问的 key 不存在的话 integerForKey 的返回值是 0, 这也会有可能会对我们真正的逻辑造成干扰。
registerDefaults用法及原理
但是按照之前objectForKey方式来解决不存在的key值问题,但显得代码比较复杂,其实我们可以用另一种方法registerDefaults来解决
[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"maxCount":@3}];
NSInteger count = [[NSUserDefaults standardUserDefaults] integerForKey:@"maxCount"]
我们在使用 integerForKey 获取 maxCount 的值之前,先调用 registerDefaults 方法设置了它的默认值。 因为我们并没有用 setXXX 方法设置 maxCount, 所以这次取值应该是空的, 但由于在他之前又使用 registerDefaults 注册了默认值。 那么这时, 我们就会得到默认值 3 了。
怎么样,这个特性大家应该并不常见。还有一点要注意的,就是 registerDefaults 设置的默认值是不会持久化存储的,也就是说我们每次启动 APP 的时候,都需要这样设置一遍。
主要原理
NSUserDefaults 还有一个 Domain 的概念,当我们调用 NSUserDefaults.standardUserDefaults() 方法时,就会初始化 NSUserDefaults, 并且它默认会包含 5 个 Domain, 分别是:
- NSArgumentDomain
- Application
- NSGlobalDomain
- Languages
- NSRegistrationDomain
大家看到这里可能有些疑惑,这些东西是干嘛用的,当我们调用类似的方法时
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isLogin"];
都是在 Application 这个域上面存储的,但 NSUserDefaults 还包括了其他 4 个域,那么为什么要有域这样的设计呢。这可以从 registerDefaults 方法说起。 registerDefaults 方法我们刚刚看到了,可以为指定的 key 注册默认值。但我们深入思考一下,这个默认值又是怎么存储和实现的呢?
其实 registerDefaults 所做的事情非常简单,只是将我们传递给它的参数都设置到了 NSRegistrationDomain 这个域中。 然后我们每次调用 NSUserDefaults.standardUserDefaults().integerForKey("maxCount") 这样的读取数据的方法时,实际上会在底层的存储结构中进行一次搜索,属性就是这样:
NSArgumentDomain -> Application -> NSGlobalDomain -> Languages -> NSRegistrationDomain
比如我们例子中的 maxCount, 由于我们之前使用 registerDefaults 将它设置到了 NSRegistrationDomain 域中,并且由于我们没有调用 setInteger 方法将它设置到 Application 域中。
NSArgumentDomain这个是命令行参数,它的优先级最高