一、NSUserDefaults是专门用来存储App配置信息的,一般不要在偏好设置中保存其他数据。是一个plist,名字为Bundle identifier名字,如cn.com.xiaoli.XLDemo.plist
二、存储位置
沙盒--> Library-->Preferences: *.plist
NSString *preferencePath = NSSearchPathForDirectoriesInDomains(NSPreferencePanesDirectory, NSUserDomainMask, YES).firstObject;
三、支持持久化类型
NSArray(含NSMutableArray)、NSDictionary(含NSMutableDictionary)、NSData(含NSMutableData)、NSString(含NSMutableString)、NSNumber、NSDate、BOOL。但存储的对象全是不可变的。
四、NSUserDefaults的域:
UserDefaults数据库中其实是由多个层级的域组成的,当你读取一个键值的数据时,NSUserDefaults从上到下透过域的层级寻找正确的值,不同的域有不同的功能,有些域是可持久的,有些域则不行。默认包含五个Domain,如下:
1、参数域( argument domain)有最高优先权
2、应用域(application domain)是最重要的域,它存储着你app通过NSUserDefaults set...forKey添加的设置
3、全局域(global domain)则存储着系统的设置
4、语言域(language-specific domains)则包括地区、日期等
5、注册域(registration domain)仅有较低的优先权,只有在应用域没有找到值时才从注册域去寻找
例如,当某个界面是第一次运行时,这时的key值(键)所对应的值还不存在,所以NSUserDefaults对象会使用默认值为0。但是对于某些设置,可能需要临时的、非0的默认值(如"出厂设置"),否则应用将无法正确的运行。一般默认值都是存放于注册域中。默认情况下,应用域是空的,没有键也没有值。当用户第一次修改某个设置时,相应的值会通过指定的键加入应用域。当通过NSUserDefaults对象获取某个设置的值时,该对象先会在应用域查找,如果没有,NSUserDefaults则会在注册域中查找并返回默认值。查询顺序:参数域 -> 应用域 -> 全局域 -> 语言域 ->注册域。
五、api使用方法(类似NSMutableDictionary)
//获取标准用户默认对象(配置为搜索当前应用程序搜索列表的NSUserDefaults的全局实例)
@property (class, readonly, strong) NSUserDefaults *standardUserDefaults;
//重置
+ (void)resetStandardUserDefaults;
// 用应用程序和当前用户的默认值初始化用户默认对象
- (nullable instancetype)initWithSuiteName:(nullable NSString *)suitename API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
- (nullable id)initWithUser:(NSString *)username API_DEPRECATED("Use -init instead", macos(10.0,10.9), ios(2.0,7.0), watchos(2.0,2.0), tvos(9.0,9.0));
//存
- (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;
- (void)setURL:(nullable NSURL *)url forKey:(NSString *)defaultName API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
存完后要调用synchronize方法,如果没有调用,系统会根据I/O情况不定时刻地保存到文件中,会造成数据丢失。所以如果需要立即写入文件的就必须调用synchronize方法。
//取
- (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;
- (BOOL)boolForKey:(NSString *)defaultName;
- (nullable NSURL *)URLForKey:(NSString *)defaultName API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
//删(同样调用synchronize方法)
// -removeObjectForKey: is equivalent to -[... setObject:nil forKey:defaultName]
- (void)removeObjectForKey:(NSString *)defaultName;
注册默认值
//将指定字典的内容添加到注册域
- (void)registerDefaults:(NSDictionary<NSString *, id> *)registrationDictionary;
//维护套间
//将指定的域名插入到接收方的搜索列表中
- (void)addSuiteNamed:(NSString *)suiteName;
//从接收器的搜索列表中移除指定的域名
- (void)removeSuiteNamed:(NSString *)suiteName;
返回包含搜索列表中域中所有键值对的联合的字典
- (NSDictionary<NSString *, id> *)dictionaryRepresentation;
维护易失性域
//易失性域名
@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;
//维护持久域
//返回当前持久域名的数组(不推荐使用
- (NSArray *)persistentDomainNames API_DEPRECATED("Not recommended", macos(10.0,10.9), ios(2.0,7.0), watchos(2.0,2.0), tvos(9.0,9.0));
//返回指定域默认值的字典表示形式
- (nullable NSDictionary<NSString *, id> *)persistentDomainForName:(NSString *)domainName;
//为指定的持久域设置字典
- (void)setPersistentDomain:(NSDictionary<NSString *, id> *)domain forName:(NSString *)domainName;
//从用户的默认值中移除指定的持久域的内容
- (void)removePersistentDomainForName:(NSString *)domainName;
//访问托管环境密钥
//返回一个布尔值,该值指示指定密钥是否由管理员管理
- (BOOL)objectIsForcedForKey:(NSString *)key;
//返回一个布尔值,该值指示指定域中的密钥是否由管理员管理
- (BOOL)objectIsForcedForKey:(NSString *)key inDomain:(NSString *)domain;
//域
//由所有应用程序所看到的默认值组成的域(全局域)
FOUNDATION_EXPORT NSString * const NSGlobalDomain;
//从应用程序的参数解析默认值组成的域(参数域)
FOUNDATION_EXPORT NSString * const NSArgumentDomain;
//由一组临时缺省值组成的域,其值可以由应用程序设置,以确保搜索始终是成功的(注册域)
FOUNDATION_EXPORT NSString * const NSRegistrationDomain;
//相关通知
//当用户默认数据中存储更多数据时
FOUNDATION_EXPORT NSNotificationName const NSUserDefaultsSizeLimitExceededNotification API_AVAILABLE(ios(9.3), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
//当设置了云默认时,但没有登录iCloud用户
FOUNDATION_EXPORT NSNotificationName const NSUbiquitousUserDefaultsNoCloudAccountNotification API_AVAILABLE(ios(9.3), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
//当用户更改主iCloud帐户时
FOUNDATION_EXPORT NSNotificationName const NSUbiquitousUserDefaultsDidChangeAccountsNotification API_AVAILABLE(ios(9.3), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
//发布时,默认完成下载数据,无论是第一次设备连接到一个iCloud帐户,或当用户切换他们的主要iCloud帐户
FOUNDATION_EXPORT NSNotificationName const NSUbiquitousUserDefaultsCompletedInitialSyncNotification API_AVAILABLE(ios(9.3), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
//在当前进程中更改用户默认值时发布
FOUNDATION_EXPORT NSNotificationName const NSUserDefaultsDidChangeNotification;
例子:
//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"qwert" forKey:@"testKey"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *testKey = [userDefaults objectForKey:@"testKey"];
NSLog(@"%@", testKey);
//4.删除文件
[userDefaults removeObjectForKey:@"testKey"];
[userDefaults synchronize];
五、思考与弃用
项目中的一些简易信息是使用NSUserDefaults存的,最近收到用户反馈之前存的东西找不到了,检查了代码很多次也没有查出问题,便怀疑NSUserDefaults方式的持久化出问题了。
经过实际测试如果存储后立即退出或者意外闪退,NSUserDefaults的数据会丢失。
翻看文档是异步存储:
/*!
-setObject:forKey: immediately stores a value (or removes the value if nil is passed as the value) for the provided key in the search list entry for the receiver's suite name in the current user and any host, then asynchronously stores the value persistently, where it is made available to other processes.
*/
- (void)setObject:(nullable id)value forKey:(NSString *)defaultName;
//旧版本中的强制同步在iOS14中不建议用,iOS15中已经弃用
/*!
-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;
建议换个轻量型数据库处理吧