NSUserDefaults的使用

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存储,需要注意的是,即使对象是NSArrayNSDictionary,他们存储的类型也应该是以上范围包括的。
  往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
    
比如,我们经常保存用户的登录信息,如:是否登录:
    UserDefaults.standard.set(true, forKey:"isLogin")
    设置完成后,这个配置就保存在 User Default System的数据库中了,我们在以后需要的时候可以通过这样的方式来取得它的值:
    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.


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值