iOS-NSUserDefaults详解

7,671

1.官方介绍

1.1 官方文档

NSUserDefaults

An interface to the user’s defaults database, where you store key-value pairs persistently across launches of your app.

用户默认数据库的接口,您可以在应用程序的启动过程中持久存储键值对。

当系统调用[[NSUserDefaults standardUserDefaults] setObject:@"" forKey:@""]后系统会为用户在沙盒下的Libray/Preferences目录下创建plist文件,文件名为当前应用的Bundle Identifier[[NSBundle mainBundle] bundleIdentifier]用户可以通过NSUserDefaults接口的参数获取到该文件夹下的数据.

1.2 NSUserDefaults数据读写

1.2.1 目标存储内容

The defaults system allows an app to customize its behavior to match a user’s preferences.

应用通过NSUserDefaults存储用户在应用内的偏好设置,比如用户设置了应用的主题颜色,使用NSUserDefaults存储后,用户再次进入应用,应用可以读取到该数据后配置应用的主题颜色(NSUserDefaults保存的数据会缓存起来所以可以实现该类似的需求.但当用户卸载了应用后,数据也将被清除,NSUserDefaults保存的数据不会通过iTunes传递到另外的设备也不会存储到iCloud上面).

因为NSUserDefaults存储的数据是写入到plist文件下,所以用NSUserDefaults存储的数据应该是小型数据而不应该是数据量大的数据.

1.2.2 数据写入

The NSUserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Boolean values, and URLs. These methods are described in Setting Default Values.

A default object must be a property list—that is, an instance of (or for collections, a combination of instances of) NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.

因为NSUserDefaults存储的数据是写入到plist文件的,所以NSUserDefaults支持存储的数据有限,包括有 NSData,NSString,NSNumber,NSDate,NSArray,NSDictionary类型的数据.通常我们使用- (void)setObject:(nullable id)value forKey:(NSString *)defaultName;来设置我们的值.但有些特殊的数据, 系统也提供了几个便利的设置值方法.

- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;
- (void)setFloat:(float)value forKey:(NSString *)defaultName;
- (void)setDouble:(double)value forKey:(NSString *)defaultName;
- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;

这里边有一个比较特殊的方法-setURL:forKey,你可以保存数据的本地地址而不需要直接保存完整数据.

/// -setURL:forKey is equivalent to -setObject:forKey: except that the value is archived to an NSData. Use -URLForKey: to retrieve values set this way.
- (void)setURL:(nullable NSURL *)url forKey:(NSString *)defaultName API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

当你存储的文件可能存在更改的情况下需要用到一下方法去建立书签存储数据.

/* Returns bookmark data for the URL, created with specified options and resource values. If this method returns nil, the optional error is populated.
 */
- (nullable NSData *)bookmarkDataWithOptions:(NSURLBookmarkCreationOptions)options includingResourceValuesForKeys:(nullable NSArray<NSURLResourceKey> *)keys relativeToURL:(nullable NSURL *)relativeURL error:(NSError **)error API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

/* Creates and Initializes an NSURL that refers to a location specified by resolving bookmark data. If this method returns nil, the optional error is populated.
 */
+ (nullable instancetype)URLByResolvingBookmarkData:(NSData *)bookmarkData options:(NSURLBookmarkResolutionOptions)options relativeToURL:(nullable NSURL *)relativeURL bookmarkDataIsStale:(BOOL * _Nullable)isStale error:(NSError **)error API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

当我们设置数据后,系统并不会立即保存到本地,而是会在一个系统觉得恰当的时间点进行存储,如果我们需要立即存储的话需要调用- (BOOL)synchronize;方法来实现.

2019.10.22更新,synchronize将在不久后的未来被废弃掉.

/*!
 -synchronize is deprecated and will be marked with the API_DEPRECATED macro in a future release.
 
 -synchronize blocks the calling thread until all in-progress set operations have completed. This is no longer necessary. Replacements for previous uses of -synchronize depend on what the intent of calling synchronize was. If you synchronized...
 - ...before reading in order to fetch updated values: remove the synchronize call
 - ...after writing in order to notify another program to read: the other program can use KVO to observe the default without needing to notify
 - ...before exiting in a non-app (command line tool, agent, or daemon) process: call CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
 - ...for any other reason: remove the synchronize call
 */
- (BOOL)synchronize;

1.2.3 数据读取

通常我们调用- (nullable id)objectForKey:(NSString *)defaultName;方法来获取我们存储的数据.系统还为我们提供了一些特别的接口供我们使用.

- (nullable NSString *)stringForKey:(NSString *)defaultName;
- (nullable NSArray *)arrayForKey:(NSString *)defaultName;
- (nullable NSDictionary<NSString *, id> *)dictionaryForKey:(NSString *)defaultName;
- (nullable NSData *)dataForKey:(NSString *)defaultName;
- (nullable NSArray<NSString *> *)stringArrayForKey:(NSString *)defaultName;

- (NSInteger)integerForKey:(NSString *)defaultName;
- (float)floatForKey:(NSString *)defaultName;
- (double)doubleForKey:(NSString *)defaultName;
- (BOOL)boolForKey:(NSString *)defaultName;
- (nullable NSURL *)URLForKey:(NSString *)defaultName API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

注意点:当我们存储的数据是可变类型时,读取后的数据将变为不可变类型.

1.2.4 数据变化监听

当我们的NSUserDefaults实例所存储的数据变更时,系统会发送NSUserDefaultsDidChangeNotification的通知,通知会返回当前更改的NSUserDefaults实例对象回来.所以当需要监听某个NSUserDefaults数据存储的数据变化时,可以添加该通知的观察者.

1.3 沙盒的注意事项

我们的应用数据时存储在沙盒目录中的,正常情况下其他应用是无法读取到我们应用的数据.但是有两种情况是被允许应用去读取其他应用的数据.

  • App extensions on macOS and iOS
  • Other apps in your application group on macOS

当我们有扩展程序或者多个应用在同一个应用组里面的时候就能被允许进行这样的操作.

当我们设置了两个应用为在同一个App Groups后,我们的代码便可以通过组名来获取应用间共享的数据了.

[[NSUserDefaults alloc] initWithSuiteName:@"group.NSUserDefaultsDemo"]

1.4 线程安全

Thread SafetyThe UserDefaults class is thread-safe. 系统在读写NSUserDefaults时是有做线程安全措施的,所以开发者在使用NSUserDefaults时是不需要考虑多线程问题.

2.API介绍

系统提供的默认存储接口

@property (class, readonly, strong) NSUserDefaults *standardUserDefaults;

清空当前默认存储的数据,数据如果被KVO监听的也将失效.

+ (void)resetStandardUserDefaults;

通过域名创建数据表,这里的suitenamenil时生成的实例将会是系统提供的默认接口,这里不应该用应用的bundle identifierNSGlobalDomain来作为参数.传入的suitename将作为identifier来存储.

- (nullable instancetype)initWithSuiteName:(nullable NSString *)suitename

返回当前所有的数据

- (NSDictionary<NSString *, id> *)dictionaryRepresentation;

移除当前key以及对应的值,这个方法和-[... setObject:nil forKey:defaultName]是等价的

- (void)removeObjectForKey:(NSString *)defaultName;

添加域名(当添加了另一个域名后,可以该实例查询数据时也将会在此域名下查询,优先在当前域名查询,结束后在其他子域名查询)

- (void)addSuiteNamed:(NSString *)suiteName;

移除域名

- (void)removeSuiteNamed:(NSString *)suiteName;

注册字典数据,常放在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions调用.当你需要事先存储一些基本数据供应用读取时,就可以通过此方法来配置了.

  • 注意的是如果字典里包含有了更改过后的Key,该Key将不会再被写入,比如字典中有userName这个Key,Value10001的默认值,当再应用内更改了userName的值后,下次再进入应用,该Key不会再赋值为10001了.
- (void)registerDefaults:(NSDictionary<NSString *, id> *)registrationDictionary;

不稳定域相关接口

/// 不稳定域的不稳定域名数组(包括了NSRegistrationDomain,
    NSArgumentDomain)
@property (readonly, copy) NSArray<NSString *> *volatileDomainNames;

/// 获取某个不稳定域名下对应的数据
- (NSDictionary<NSString *, id> *)volatileDomainForName:(NSString *)domainName;

/// 通过域名以及字典数据设置不稳定域
- (void)setVolatileDomain:(NSDictionary<NSString *, id> *)domain forName:(NSString *)domainName;

/// 通过域名以及不稳定域
- (void)removeVolatileDomainForName:(NSString *)domainName;

稳定域相关接口,与不稳定域类似

- (nullable NSDictionary<NSString *, id> *)persistentDomainForName:(NSString *)domainName;
- (void)setPersistentDomain:(NSDictionary<NSString *, id> *)domain forName:(NSString *)domainName;
- (void)removePersistentDomainForName:(NSString *)domainName;

注意点

  • 稳定域将会写入本地Preferences文件夹里面生成对应的plist文件,不稳定域将不会生成.
  • initWithSuiteName生成的为稳定域

3.域

NSUserDefault中存在域的概念,包含5个部分NSArgumentDomainApplicationNSGlobalDomainLanguagesNSRegistrationDomain

  • NSArgumentDomain:代表的是命令行参数,可以在Edit Scheme->Arguments->Arguments Passed On Launch中添加,格式是-key value
  • Application: 应用程序域,设置的方法默认数据保存是在这里
  • NSGlobalDomain: 全局域,所有应用程序都将公用该域
  • Languages: 国际化语言版本域
  • NSRegistrationDomain: 临时域,- (void)registerDefaults:(NSDictionary*)registrationDictionary方法被调用是数据是保存在这里。在读取数据时,都会在底层的存储结构中进行一次搜索,搜索的顺序是这样:
NSArgumentDomain->Application->NSGlobalDomain->Languages->NSRegistrationDomain

4.参考

谈谈大家熟悉的NSUserDefault

iOS笔记之NSUserDefaults

iOS通过NSUserDefaults实现简单的应用间数据传递