NSUserDefaults
NSUserDefaults类提供了一个编程界面与默认系统相互作用。默认系统允许应用自定义行为来匹配用户的偏好设置或参数选择。例如:可以允许用户确定应用显示度量单位或者文档多久自动保存一次。
1) iOS数据持久化之使用NSUserDefaults存储数据,NSUserDefaults是一个单例,在整个程序中只有一个实例对象.
2) iOS下可以使用NSUserDefaults、sqlite、CoreData几种常用的方式来存储数据,其中NSUserDefaults用来存储类似用户的配置等这些的数据,后两者用户存储大批量和比较复杂的数据。
NSUserDefaults适合存储轻量级的本地数据,比如要保存一个登陆界面的数据,用户名、密码之类的,个人觉得使用NSUserDefaults是首选。下次再登陆的时候就可以直接从NSUserDefaults里面读取上次登陆的信息咯。因为如果使用自己建立的plist文件什么的,还得自己显示创建文件,读取文件,很麻烦,而是用NSUserDefaults则不用管这些东西,就像读字符串一样,直接读取就可以了。
3) NSUserDefault的使用比较简单:
NSUserDefaults单例以key-value的形式存储了一系列偏好设置,key是名称,value是相应的数据。存/取数据时可以使用方法objectForKey:和setObject:forKey:来把对象存储到相应的plist文件中,或者读取,既然是plist文件,那么对象的类型则必须是plist文件可以存储的类型。它支持的数据类型有NSString、NSNumber、NSDate、 NSArray、NSDictionary、BOOL、NSInteger、NSFloat等系统定义的数据类型,如果要存放自定义的对象(如自定义的类对象),则必须将其转换成NSData存储,需要注意的是,即使对象是NSArray或NSDictionary,他们存储的类型也应该是以上范围包括的。
往NSUserDefaults添加数据后,它们就变成了全局的变量,App中即可读写NSUserDefault中的数据:如果想删除某个数据项,可以使用removeObjectForKey删除数据:
需要注意的是,NSUserDefaults是定时把缓存中的数据写入磁盘的,而不是即时写入,为了防止在写完NSUserDefaults后程序退出导致的数据丢失,可以在写入数据后使用synchronize强制立即将数据写入磁盘。
// 注意:对相同的Key赋值约等于一次覆盖,要保证每一个Key的唯一性.值得注意的是:
NSUserDefaults存储的对象全是不可变的(这一点非常关键,弄错的话程序会出bug),例如,如果我想要存储一个 NSMutableArray对象,我必须先创建一个不可变数组(NSArray)再将它存入NSUserDefaults中去,取出数据是一样的,想要用NSUserDefaults中的数据给可变数组赋值.
4)NSUserDefaults类是线程安全的。
一:看一个小demo 使用NSUserDefaults实现填写几个信息和获取系统相册库图片同时存储。
#import "ViewController.h"
@interfaceViewController ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate,UIAlertViewDelegate>
此部分是在Main.storyboard中拖控件并进行连线相关联获取的。
@property (weak,nonatomic)IBOutlet UITextField *firstNameTextfield;
@property (weak,nonatomic)IBOutlet UITextField *secondNameTextfield;
@property (weak,nonatomic)IBOutlet UITextField *ageTextfeild;
@property (weak,nonatomic)IBOutlet UIImageView *chooseImageView;
- (IBAction)save:(id)sender;
- (IBAction)ChooseImage:(id)sender;
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *firstName = [defaults objectForKey:@"firstName"];
NSString *lastName = [defaults objectForKey:@"secondName"];
NSInteger age = [defaults integerForKey:@"age"];
NSString *ageString = [NSStringstringWithFormat:@"%ld",(long)age];
NSData *imageData = [defaults dataForKey:@"image"];
UIImage *contactImage = [UIImageimageWithData:imageData];
// Update the UI elements with the saved data
self.firstNameTextfield.text = firstName;
self.secondNameTextfield.text = lastName;
self.ageTextfeild.text = ageString;
self.chooseImageView.image = contactImage;
}
- (IBAction)save:(id)sender {
// Hide the keyboard去除键盘
[self.firstNameTextfield resignFirstResponder];
[self.secondNameTextfield resignFirstResponder];
[self.ageTextfeild resignFirstResponder];
// Create strings and integer to store the text info
NSString *firstName = [self.firstNameTextfield text];
NSString *secondName = [self.secondNameTextfield text];
NSInteger age = [[self.ageTextfeild text]integerValue];
// Create instances of NSData把图片转为NSData进行存储
UIImage *contactImage =self.chooseImageView.image;
NSData *imageData =UIImageJPEGRepresentation(contactImage,100);
// Store the data保存数据
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:firstName forKey:@"firstName"];
[defaults
setObject
:secondName
forKey
:
@"secondName
”
];
[defaults setInteger:age forKey:@"age"];
[defaults setObject:imageData forKey:@"image"];
[defaults synchronize];
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"温馨提示"message:@"数据保存成功!"delegate:self cancelButtonTitle:nil otherButtonTitles:@"确定",nil];
[alert show];
}
- (IBAction)ChooseImage:(id)sender {
UIImagePickerController *picker=[[UIImagePickerController alloc]init];
picker.delegate=self;//设置代理
picker.allowsEditing=YES;//允许编辑
picker.sourceType=UIImagePickerControllerSourceTypePhotoLibrary;
[self presentViewController:pickeranimated:YEScompletion:^{}];
}
#pragma mark---Image Picker Delegate--------
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage];
self.chooseImageView.image = image;
[picker dismissViewControllerAnimated:YES completion:^{}];
}
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
[picker dismissViewControllerAnimated:YES completion:^{ }];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
NSLog(@"成功!");
}
@end
效果为:
二:对自定义对象进行存储
如果要把一个自定义对象能转换成为NSData.需要在自定义对象上实现NSObject和NSCoding协议,并且实现func encodeWithCoder(aCoder: NSCoder)和init?(coder aDecoder: NSCoder)方法。所有自定义一个学生类,有三个属性名字,年龄,性别如下:
class Student: NSObject,NSCoding {
var name:String?
var age:Int?
var sex:String?
override init() {
print("init Student")
}
init(name:String?,age:Int?,sex:String?) {
self.name = name
self.age = age
self.sex = sex
}
//将Student类型变成NSData类型,所以必须实现归档:
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.sex, forKey: "sex")
aCoder.encode(self.age, forKey: "age")
}
//解档即解码
required init?(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as? String
self.sex = aDecoder.decodeObject(forKey: "sex") as? String
self.age = aDecoder.decodeObject(forKey: "age") as? Int
}
}
接下来,在ViewController.swift进行测试,归档单个学生信息并在归档之后获取学生信息,具体代码如下:
func testCustomObjectArchived(){
let userDefault = UserDefaults.standard
let key = "StudentData"
//创建学生类,并赋值
let student = Student()
student.name = "shihua"
student.age = 24
student.sex = "male"
//也可以初始化时指定值
//let student = Student(name: "shihua", age: 24, sex: "male")
//归档学生类
let data = NSKeyedArchiver.archivedData(withRootObject: student)
print("data = \(data)")//data = 303 bytes
userDefault.set(data, forKey:key)
//判断是否存储成功
if userDefault.synchronize() {
if let studentData = userDefault.object(forKey: key) as? NSData{
//解档数据,转换为对应的学生类
let student = NSKeyedUnarchiver.unarchiveObject(with: studentData as Data) as! Student
print("name = \(student.name),age = \(student.age),sex = \(student.sex)")//name = Optional("shihua"),age = Optional(24),sex = Optional("male")
}
}
}
如果想存储多个学生的信息,只要创建多个学生并使用数组存储,最后对数组归档即可,如下:
func saveManyStudentData(){
let userDefault = UserDefaults.standard
let key = "StudentData"
var arr = [Student]()
//存储5个学生数据
for _ in 0...4 {
let student = Student(name: "shihua", age: 24, sex: "male")
arr.append(student)
}
//归档学生类
let data = NSKeyedArchiver.archivedData(withRootObject: arr)
print("data = \(data)")//data = 692 bytes
userDefault.set(data, forKey:key)
//判断是否存储成功
if userDefault.synchronize() {
if let studentData = userDefault.object(forKey: key) as? NSData{
//解档数据,转换为对应的学生类
let array = NSKeyedUnarchiver.unarchiveObject(with: studentData as Data) as! Array<Student>
for i in 0..<array.count {
print("name = \(array[i].name),age = \(array[i].age),sex = \(array[i].sex)")//打印5次:name = Optional("shihua"),age = Optional(24),sex = Optional("male")
}
}
}
}
三:使用register(defaults registrationDictionary: [String :Any])方法,注册默认值
该方法是添加一个具体的字典到注册域,当将key传递给 UserDefaults 的object(forKey defaultName: String)方法时,它会经历这样一个过程。 因为UserDefaults数据库中其实是由多个层级的域组成的,当你读取一个键值的数据时,NSUserDefaults从上到下透过域的层级寻找正确的值,不同的域有不同的功能,有些域是可持久的,有些域则不行。并且它默认会包含5 个 Domain, 分别是:
NSArgumentDomain: 参数域,有最高优先权
Application: 应用域(application domain)是最重要的域,它存储着你app通过UserDefaults set...forKey添加的设置
NSGlobalDomain:全局域(global domain)则存储着系统的设置
Languages: 语言域(language-specific domains)则包括地区、日期等
NSRegistrationDomain: 注册域(registration domain)仅有较低的优先权,只有在应用域没有找到值时才从注册域去寻找
简单来说, 我们调用的类似这样的方法:UserDefaults.standard.set(true, forKey: "isLogin”)
都是在Application这个域上面存储的,但 UserDefaults还包括了其他4个域,那么为什么要有域这样的设计呢。这可以从register方法说起。 register方法我们刚刚看到了,可以为指定的key注册默认值。但我们深入思考一下,这个默认值又是怎么存储和实现的呢?
其实 register方法所做的事情非常简单,只是将我们传递给它的参数都设置到了NSRegistrationDomain 这个域中。 然后我们每次调用 UserDefaults.standard.bool(forKey:"isLogin")这样的读取数据的方法时,实际上会在底层的存储结构中进行一次搜索,属性搜索过程就是这样:NSArgumentDomain -> Application -> NSGlobalDomain -> Languages -> NSRegistrationDomain
举个例子:
UserDefaults.standard.register(defaults: ["FloatValue":3.0])
UserDefaults.standard.float(forKey:"FloatValue")
比如我们例子中的FloatValue,由于我们之前使用 register将它设置到了NSRegistrationDomain 域中,并且由于我们没有调用 set(_value:Float, forKey defaultName:String)方法将它设置到Application 域中。 所以按照 UserDefaults的默认搜索顺序,就会找到最后NSRegistrationDomain 域中的那个 FloatValue,也就是我们所谓的默认值 3.0 了。相反,如果设置了值,那么就会从Application中获取值,就是我们日常使用情景。
注意,就是 registerDefaults设置的默认值是不会持久化存储的,也就是说我们每次启动 APP的时候,都需要这样设置一遍。
那说了这么多,到底有什么用了?其实就是在使用UserDefaults相关获取值方法的时候,要注意在没有设置值之前如果去获取值,取得的值可能对自己的业务逻辑造成影响。
openfunc integer(forKey defaultName: String) ->Int
openfunc float(forKey defaultName:String) ->Float
openfunc double(forKey defaultName:String) ->Double
openfunc bool(forKey defaultName:String) ->Bool
openfunc integer(forKey defaultName: String) ->Int
openfunc float(forKey defaultName:String) ->Float
openfunc double(forKey defaultName:String) ->Double
openfunc bool(forKey defaultName:String) ->Bool
比如,我们经常保存用户的登录信息,如:是否登录:
UserDefaults.standard.set(true, forKey:"isLogin")
设置完成后,这个配置就保存在 User Default System的数据库中了,我们在以后需要的时候可以通过这样的方式来取得它的值:
UserDefaults.standard.bool(forKey: "isLogin")
这样使用很正常,但是有一些细节需要注意那就是,如果在获取 isLogin的值之前它并没有被设置,会返回什么呢?如果使用 bool方法,对于不存在的 key,返回的是false。这是一个默认值,但并不一定符合你的业务逻辑。比如你需要对 isLogin 真正为false的时候进行某些处理,但bool会对不存在的key也返回false。这种情况下,它就会扰乱你的业务逻辑。
UserDefaults.standard.bool(forKey: "isLogin")
这样使用很正常,但是有一些细节需要注意那就是,如果在获取 isLogin的值之前它并没有被设置,会返回什么呢?如果使用 bool方法,对于不存在的 key,返回的是false。这是一个默认值,但并不一定符合你的业务逻辑。比如你需要对 isLogin 真正为false的时候进行某些处理,但bool会对不存在的key也返回false。这种情况下,它就会扰乱你的业务逻辑。
所以可以使用注册默认值,对默认值做判断从而避免不必要的麻烦。当然对于布尔值不是很好操作,如果是使用integer类型,可以指定默认值,可以确保为0时不会造成混乱。所以具体情况具体分析,如果使用openfunc object(forKey defaultName: String) ->Any?方法,那也是ok的。UserDefaults.standard().object(forKey:"isLogin"),因为没有设置返回为nil.