前言

NSUserDefaults作为iOS內建的存储机制早已为大家所熟知。NSUserDefaults在少量数据持久化和用户偏好存储方面一直发挥着重大的作用。对于NSUserDefaults的常见用法应该是无外乎存取数据。相比较NSUserDefaults,Xcode中的环境变量就比较陌生。有时候,从头到尾开发一款APP也不一定会用到环境变量。而实际上NSUserDefaults和环境变量存在着千丝万缕的关系。本篇文章将会揭开二者的内在联系。

NSUserDefaults

NSUserDefaults的常见用法,如下:

1
2
[[NSUserDefaults standardUserDefaults] setObject:@(1) forKey:@"isVip"];
[[NSUserDefaults standardUserDefaults] objectForKey:@"isVip"];

synchronize

NSUserDefaults向磁盘中写入数据不是实时的。有时候我们出于安全(比如担心程序异常退出而导致数据未能及时持久化)和业务上(比如持久化的数据需要立即在另一个地方读取)的需要,希望执行了[[NSUserDefaults standardUserDefaults] setObject:@(1) forKey:@”isVip”]之后,数据能够立即持久化到磁盘中,在这种场景下,我们通常会调用下synchronize。
另外NSUserDefaults只支持系统类型,比如NSString, NSNumber, NSDate, NSArray, NSDictionary。不支持自定义类型。

有时候,我们也可能会用到NSUserDefaults的-(BOOL)synchronize方法,但请注意,这个方法将会在不就之后被废弃,Apple官方不推荐使用synchronize来实现数据的同步。废弃的原因是:synchronize方法会阻塞当前线程直到所有的数据都持久化之后才会返回。

Arguments Passed On Launch

我们新建一个名为NSUserDefaults的iOS项目,然后点击Xcode左上角的项目名称,点击【Edit Scheme…】,如下:

进入Edit Scheme面板,依次选择Run->Arguments->Arguments Passed On Launch,如下:

然后在Arguments Passed On Launch下点击“+”来新增”-cityId 1”,如下:

然后使用NSUserDefaults来取得这个参数:

1
2
3
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id cityId = [defaults objectForKey:@"cityId"];
NSLog(@"cityId: %@", cityId); // cityId: 1

可见,我们可以通过在Xcode的Edit Scheme配置一些启动参数。当程序运行起来时,Xcode的会自动把这些参数设置给NSUserDefaults。但是这仅限于Xcode启动APP,如果通过点击APP 图标的方式启动APP,那么NSUserDefaults并不会接收到传递的参数。

NSUserDefaults域

NSUserDefaults对象底层包含5个domain(域),分别是:

  • NSArgumentDomain
  • Application
  • NSGlobalDomain
  • Languages
  • NSRegistrationDomain

NSUserDefaults的这5个域从上向下优先级一次递减,我们的[[NSUserDefaults standardUserDefaults] setObject:@(1) forKey:@”isVip”];默认会把数据写到Application这个域中。
当通过id cityId = [defaults objectForKey:@”cityId”];读取的时候,会按照NSArgumentDomain -> Application -> NSGlobalDomain -> Languages -> NSRegistrationDomain的优先级顺序依次搜索每个域,如果在某个域检索到对应的value,那么就直接返回,否则会一直向下一个domain搜索,直到搜索到NSRegistrationDomain这个域为止。
NSUserDefaults的这种按域分层设计:可以实现参数覆盖。因为5个域之间存在访问优先级,这样就可以实现高优先级域的数据覆盖低优先级域数据。这样又有什么用处呢?
比如,Xcode启动传参的数据是存储在NSArgumentDomain这个域中的,APP的默认语言是存储在Languages这域中的。而这个Languages域的优先级低于NSArgumentDomain,这样就可以通过Xcode的启动传参来实现APP语言的设置优先使用Xcode的传参配置。

另外,NSUserDefaults还有registerDefaults功能。一旦通过registerDefaults为某个key注册了默认值,那么通过key取值时,在value为空的情况下,NSUserDefaults会为我们返回默认值,使用方法如下:

1
2
3
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults registerDefaults:@{@"cityId": @(0)}];
id cityId = [defaults objectForKey:@"cityId"];

那么他又是怎么实现的呢?其实道理很简单,前面已经讲过了NSUserDefaults的5个域的概念。其中最后一个域也就是搜索优先级最低的域叫做NSRegistrationDomain。我们所有通过registerDefaults方法注册的默认值都会设置到这个域中。当按照优先级顺序从上向下访问每个域时,如果最终访问到了
NSRegistrationDomain,则说明前面4个域都没有对应key的value,那么就会返回NSRegistrationDomain中注册的默认值。

总结

可见,NSUserDefaults不仅实现了简单的存储。其内部的按域分层设计和Xcode的启动传参为我们的开发带来了更多的可能。

敬请期待~

参考文章

NSUserDefaults - 熟悉与陌生