UI加强(二)之block的用法以及数据本地存储(数据持久化)
1.block的用法之block的封装:
什么时候用block进行封装?有相同部分,并且不同部分是代码块,就可以尝试用block进行代码封装.
#pragma mark -- 点击编辑按钮 - (IBAction)didClickEditBtn:(id)sender { if (_saveBtn.enabled) { _editItem.title = @"编辑"; _nameText.enabled = NO; _phoneText.enabled = NO; _saveBtn.enabled = NO; //撤销第一响应者身份 [self.view endEditing:YES]; //恢复原有数据 _nameText.text = _listModel.name; _phoneText.text = _listModel.number; }else{ _editItem.title = @"取消"; _nameText.enabled = YES; _phoneText.enabled = YES; _saveBtn.enabled = YES; //name成为第一响应者 [_nameText becomeFirstResponder]; } }
例如:上例中if else语句中控件的状态出现了重复,这样就可以用block进行代码的封装:
如下代码所示:
#pragma mark -- 点击编辑按钮 - (IBAction)didClickEditBtn:(id)sender { if (_saveBtn.enabled) { _editItem.title = @"编辑"; // _nameText.enabled = NO; // _phoneText.enabled = NO; // _saveBtn.enabled = NO; [self changeUIState:NO andBlock:^{ _editItem.title = @"编辑"; }]; //撤销第一响应者身份 [self.view endEditing:YES]; //恢复原有数据 _nameText.text = _listModel.name; _phoneText.text = _listModel.number; }else{ // _editItem.title = @"取消"; // _nameText.enabled = YES; // _phoneText.enabled = YES; // _saveBtn.enabled = YES; //name成为第一响应者 // [_nameText becomeFirstResponder]; [self changeUIState:YES andBlock:^{ _editItem.title = @"取消"; [_nameText becomeFirstResponder]; }]; } } #pragma mark -- block的封装 - (void)changeUIState:(BOOL)state andBlock:(void(^)())block{ _nameText.enabled = state; _phoneText.enabled = state; _saveBtn.enabled = state; if (block) { block(); } }
务必要注意:红色部分不能忘记写,因为这是block代码块执行的标志,没有他是不会执行封装中的代码块的.
2.block使用为什么用copy修饰
默认情况下,block是存档在栈中,可能被随时回收,通过copy操作可以使其在堆中保留一份, 相当于一直强引用着, 因此如果block中用到self时, 需要将其弱化, 通过__weak或者__unsafe_unretained. 以下是示例代码及其说明, 读者可以试着打印出不同情况下block的内存情况
#import "ViewController.h" @interface ViewController () @property (nonatomic, copy) void(^myblock)(); @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //1 __NSGlobalBlock__ 全局block 存储在代码区(存储方法或者函数) void(^myBlock1)() = ^() { NSLog(@"我是老大"); }; NSLog(@"%@",myBlock1); //2 __NSStackBlock__ 栈block 存储在栈区 //block内部访问外部变量 //block的本质是一个结构体 int n = 5; void(^myBlock2)() = ^() { NSLog(@"我是老二%d", n); }; NSLog(@"%@", myBlock2); //3 __NSMallocBlock__ 堆block 存储在堆区 对栈block做一次copy操作 void(^myBlock3)() = ^() { NSLog(@"我是老二%d", n); }; NSLog(@"%@", [myBlock3 copy]); /* 由以上三个例子可以看出当block没有访问外界的变量时,是存储在代码区, 当block访问外界变量时时存储在栈区, 而此时的block出了作用域就会被释放 以下示例: */ [self test];//当此代码结束时,test函数中的所有存储在栈区的变量都会被系统释放, 因此如果属性的block是用assign修饰时 当再次访问时就会出现野指针访问. self.myblock(); } - (void)test { int n = 5; [self setMyblock:^{ NSLog(@"%d",n); }]; NSLog(@"test--%@",self.myblock); } @end
2.block使用之逆向传值(可以结合代理分析)
首先明白一点,谁传值谁就去定义block,这里的示例是我想在添加联系人界面上把textfield中的联系人和电话传值到联系人列表界面上,OK,怎么传?
那么就在添加联系人.h文件中定义block
#import <UIKit/UIKit.h> @class AddViewController,ListModel; #pragma mark -- 协议 //@protocol AddViewControllerDelegate <NSObject> //代理方法 //- (void)addViewController:(AddViewController *)addController addFinishAdd:(ListModel *)listModel; //@end @interface AddViewController : UIViewController //id类型的成员属性 //@property (nonatomic,weak)id<AddViewControllerDelegate>delegate; //改用block传值 @property(nonatomic,copy)void(^addBlock)(ListModel *); @end
在.m文件中进行判断执行block
- (IBAction)saveBtn:(id)sender { //1.让键盘失去响应 [self.view endEditing:YES]; //2.回到上一个控制器 [self.navigationController popViewControllerAnimated:YES]; //3.传递数据 ListModel *listModel = [[ListModel alloc]init]; listModel.name = _nameTextField.text; listModel.number = _phoneNumTextField.text; //4.代理方法 // if ([self.delegate respondsToSelector:@selector(addViewController:addFinishAdd:)]) { // [self.delegate addViewController:self addFinishAdd:listModel]; // } if (_addBlock) { _addBlock(listModel); } }
在联系人列表控制器中对block进行赋值操作:
#pragma mark -- 无论是手动segue还是自动segue都会调用这个方法,目的是获取目标控制器 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ if ([segue.identifier isEqualToString:@"ListToAdd"]) { //跳入添加联系人界面 AddViewController *addController = segue.destinationViewController; //设置目标控制器的代理对象 // addController.delegate = self; //先给block代码块赋值 addController.addBlock = ^(ListModel *listModel){ [self.dataArray addObject:listModel]; [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationBottom]; };
分析,我为什么要在prepareForSegue这个方法中写block的赋值?
因为block是addController的属性,只有这里可以获取addController对象,所以要在这里写.
block的小应用:
// // ViewController.m // block的小练习 // // Created by 曹魏 on 2016/11/28. // Copyright © 2016年 itcast. All rights reserved. // #import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self configerButtom:^(UIButton *btn) { [btn setTitle:@"点我啊" forState:UIControlStateNormal]; }]; } //写一个带有block参数的方法 - (void)configerButtom:(void(^)(UIButton *btn))tempBlock{ UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 100, 100)]; //设置btn的背景颜色 btn.backgroundColor = [UIColor grayColor]; [self.view addSubview:btn]; if (tempBlock) { tempBlock(btn); } } @end
3.数据存储方式
- XML属性列表(plist)归档
- Preference(偏好设置)
本质还是通过“plist”来存储数据, 但是使用更简单(无需关注文件、文件夹路径和名称)
- NSKeyedArchiver归档(NSCoding)
把任何对象, 直接保存为文件的方式。
- SQLite3
当非常大量的数据存储时使用
- Core Data, Realm数据库
就是对SQLite的封装
3.1数据存储之沙盒目录
沙盒就是一个装数据的目录
这里只需要知道两行代码:
1.bundle目录(安装目录)
// bundle 目录 NSLog(@"%@", [NSBundle mainBundle].bundlePath);
2.沙盒目录(存储数据目录)
// 访问沙盒的目录 NSLog(@"%@", NSHomeDirectory());
沙盒目录包括:Documents,tmp,Library(Caches,Preference)
Documents保存程序运行时需要持久化的数据,itunes同步时会自动备份该数据,是相对重要的数据,例如游戏存档数据,否则数据丢失,丧失游戏体验。
tmp保存临时数据,一般比较大,iTunes不会自动备份,并且数据在程序结束后被清除,甚至在应用没有运行时被系统清除
Library/Caches保存需要持久化的数据,但不会被iTunes备份,数据比较大且不重要,有点鸡肋
Library/Preference 保存所有偏好设置,会被iTunes备份,但该目录是由系统控制,无需我们控制,通常保存记住密码和自动登录.
3.2存储方式之plist
首先,plist文件存储的数据格式是数组和字典两种格式
也就是说,我可以把数组或者字典存到plist文件里面去,这里就是要弄清楚怎么存和怎么取的问题:
1.怎么存?
用writeToFile方法存储数据,但后面是路径
[dict writeToFile:filePath atomically:YES];
接下来就是路径怎么找?
有个方法:
NSSearchPathForDirectoriesInDomains(<#NSSearchPathDirectory directory#>, <#NSSearchPathDomainMask domainMask#>, <#BOOL expandTilde#>)
该方法可以找到Documents目录下的方法.
存储数组:
#pragma mark -- 存储数据 - (IBAction)save:(id)sender { //一:存储数组数据到plist文件中 // NSMutableArray *dataArray = [NSMutableArray array]; NSArray *dataArray = @[@"🇨🇳",@"🇺🇸",@"🇫🇷",@"🇯🇵"]; NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; // //拼接路径 NSString *filePath = [docPath stringByAppendingPathComponent:@"country.plist"]; [dataArray writeToFile:filePath atomically:YES];
存储字典:
//二:存储字典数据到plist文件中 NSDictionary *dict = @{@"大鲨鱼":@"奥尼尔"}; // [dataArray addObject:dict]; NSString *docPath1 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; //拼接路径 NSString *filePath1 = [docPath1 stringByAppendingPathComponent:@"country.plist"]; [dict writeToFile:filePath1 atomically:YES];
数组中存字典:
NSMutableArray *dataArray = [NSMutableArray array]; NSDictionary *dict = @{@"大鲨鱼":@"奥尼尔"}; [dataArray addObject:dict]; NSString *docPath1 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; //拼接路径 NSString *filePath1 = [docPath1 stringByAppendingPathComponent:@"country.plist"]; [dataArray writeToFile:filePath1 atomically:YES];
拼接路径的两种方式:
// 拼接路径1:直接拼接 NSString *filePath = [doc stringByAppendingString:@"/Documents/aa.plist"]; // 拼接路径2:最简单拼接 NSString *filePath = [doc stringByAppendingPathComponent:@"Documents/aa.plist"]; 一般拼接用第二种,因为,第二种默认把/(斜杠)也拼接进去了,所以避免了我们再写/,所以很牛逼
注意document路径的方法:
// NSSearchPathForDirectoriesInDomains(<#NSSearchPathDirectory directory#>, <#NSSearchPathDomainMask domainMask#>, <#BOOL expandTilde#>)
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
第一个参数点进去的时候必须要找到document,不要盲目选择,另外NSSearch这个方法返回值是个数组类型的,所以,后面要加个取得数组中的一个元素就是document值
2.怎么取:
#pragma mark #pragma mark - 读取数据 - (IBAction)readData:(id)sender { NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"aa.plist"]; // NSArray *tempArray = [NSArray arrayWithContentsOfFile:filePath]; NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath]; NSLog(@"%@", dict); }
3.3存储方式之用户偏好设置
- 注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入[defaults synchornize];
#pragma mark -- 保存数据 - (IBAction)save:(id)sender { //封装好的字典,系统会自动帮我们把数据存储到preference目录下 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; //存放数据 [defaults setObject:@"🇺🇸" forKey:@"USA"]; [defaults setInteger:20 forKey:@"age"]; [defaults setBool:YES forKey:@"marryme"]; //执行立即写入 [defaults synchronize]; } #pragma mark -- 读取数据 - (IBAction)get:(id)sender { //1.获取数据 NSString *country = [[NSUserDefaults standardUserDefaults] objectForKey:@"USA"]; NSInteger age = [[NSUserDefaults standardUserDefaults] integerForKey:@"age"]; BOOL chose = [[NSUserDefaults standardUserDefaults] boolForKey:@"marryme"]; //2.打印数据 NSLog(@"%@,%zd,%zd",country,age,chose); //注意:%zd是可以在32位和64为下自动适配 }
用户偏好设置适合存储什么样的数据呢?
由于用户偏好设置是系统自动的把数据存储到Preference目录下的plist文件中的,所以不适合存储数据比较大和多的数据(存储大数据需要用到数据库SQLite之类),主要就是用于
存储用户登录的用户名和密码之类的数据
#pragma mark -- 数据存储 //自动登录 - (IBAction)didClickAutoSwitch:(UISwitch *)sender { //问题1:为什么要存储到Preference文件目录下? /* 自动登录 开 记住密码 开 关 */ // UISwitch if (sender.isOn) { //记住密码打开 [_recordSwitch setOn:YES animated:YES]; //存储记住密码的状态 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:KRecordSwitch]; } //存储自动登录的状态 [[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:KAutoSwitch]; //执行立即写入 [[NSUserDefaults standardUserDefaults] synchronize]; //记住密码 /* 记住密码 关 自动登录 关 */ } - (IBAction)didClickRecordSwitch:(UISwitch *)sender { if (!sender.isOn) { //自动登录关闭 [_autoSwitch setOn:NO animated:YES]; //记录自动登录的状态 [[NSUserDefaults standardUserDefaults] setBool:YES forKey: KAutoSwitch]; } //记录记住密码的状态 [[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:KRecordSwitch]; //执行立即写入 [[NSUserDefaults standardUserDefaults] synchronize]; }
通过switch控件改变输入框的状态,达到存储的目的
//读取偏好设置,设置开关状态 BOOL isRecord = [[NSUserDefaults standardUserDefaults] boolForKey:KRecordSwitch]; BOOL isAuto = [[NSUserDefaults standardUserDefaults] boolForKey:KAutoSwitch]; if (isRecord) { //打开记住密码按钮 [_recordSwitch setOn:YES animated:YES]; //取出偏好设置中存储的用户名和密码给textfield赋值 _nameField.text = [[NSUserDefaults standardUserDefaults] objectForKey:KUserName]; _passwordField.text = [[NSUserDefaults standardUserDefaults] objectForKey:KPassWord]; } if (isAuto) { //打开自动登录的开关 [_autoSwitch setOn:YES animated:YES]; //实现自动登录 [self didClickLoginBtn:nil]; }
3.4存储方式之归档解档
归档:就是存储数据,归档就是一个方法:[NSKeyedArchiver archiverRootObject:对象 toFile: 文件路径]
解档:就是读取数据,解档就是一个方法:[NSKeyedUnArchiver unarchiberObjectWithFile:文件路径]
以下示例我是要存储一个对象,本质上是存储这个对象的一些属性
//保存数据,归档 - (IBAction)didClickStoreBtn:(UIButton *)sender { Person *p = [Person new]; p.name = @"张三"; p.age = 18; // NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"person.data"]; //归档 [NSKeyedArchiver archiveRootObject:p toFile:KFilePath]; // NSLog(@"%@",KFilePath);
注意:归档就这一个方法,要注意的是两个参数:p和KFilePath,要存对象p,其实就是存储对象的一些属性,还有文件路径,也必须写出来.
} //获取数据,解档 - (IBAction)didClickGetBtn:(UIButton *)sender { //解档:就是把数据,这个对象从归档器中取出来 Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:KFilePath]; NSLog(@"%@,%zd",p.name,p.age); }
// // Person.h // 归档解档练习 // // Created by 曹魏 on 2016/11/28. // Copyright © 2016年 itcast. All rights reserved. // #import <Foundation/Foundation.h> //这里注意了:
- 如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复
- 不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以
@interface Person : NSObject<NSCoding> @property (nonatomic,copy)NSString *name; @property (nonatomic,assign)NSInteger age; @end
// // Person.m // 归档解档练习 // // Created by 曹魏 on 2016/11/28. // Copyright © 2016年 itcast. All rights reserved. // #import "Person.h" @implementation Person //归档 - (void)encodeWithCoder:(NSCoder *)aCoder{ //告诉归档器,对哪些属性进行归档 [aCoder encodeObject:_name forKey:@"name"]; [aCoder encodeInteger:_age forKey:@"age"]; } //解档 - (instancetype)initWithCoder:(NSCoder *)aDecoder{
//重写构造方法这里不要纠结了,就按照之前记忆的,遇到重写构造方法,必须要带上这个if判断
原因就是父类有可能会初始化化失败 if (self = [super init]) { self.name = [aDecoder decodeObjectForKey:@"name"]; self.age = [aDecoder decodeIntegerForKey:@"age"]; } return self; } @end
笔记:
归档和解档 NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复 1. 一般是自定义对象(模型对象), 使用归档的时候, 必须 遵守 NSCoding 协议 2. 归档 // 告诉归档器 , 要对哪些属性进行归档 - (void)encodeWithCoder:(NSCoder *)aCoder { // 要告诉归档器, 对哪些属性进行归档 [aCoder encodeObject:_name forKey:@"name"]; [aCoder encodeInteger:_age forKey:@"age"]; } 3. 解档 - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { // 为了实例化一个Person对象 // 要对哪些属性进行解档 self.name = [aDecoder decodeObjectForKey:@"name"]; self.age = [aDecoder decodeIntegerForKey:@"age"]; } return self; }
实际应用了:完美
ListViewController.m
#pragma mark -- 无论是手动segue还是自动segue都会调用这个方法,目的是获取目标控制器 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ if ([segue.identifier isEqualToString:@"ListToAdd"]) { //跳入添加联系人界面 AddViewController *addController = segue.destinationViewController; //设置目标控制器的代理对象 // addController.delegate = self; //先给block代码块赋值 addController.addBlock = ^(ListModel *listModel){ [self.dataArray addObject:listModel]; [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationBottom]; //由于在这里把数据添加到数组中了,所以我可以把数组归档 //路径,宏定义中 // [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"ListTable.data"]; [NSKeyedArchiver archiveRootObject:self.dataArray toFile:KFilePath]; };
#pragma mark -- 懒加载数组 - (NSArray *)dataArray{ if (_dataArray == nil) { // _dataArray = [NSMutableArray array]; //界面加载时要给数组赋值,获取解档出来的值 //解档 _dataArray = [NSKeyedUnarchiver unarchiveObjectWithFile:KFilePath]; if (_dataArray.count == 0) { _dataArray = [NSMutableArray array]; } } return _dataArray; }
// // ListModel.h // 通讯录小练习第一波 // // Created by 曹魏 on 2016/11/20. // Copyright © 2016年 itcast. All rights reserved. // #import <Foundation/Foundation.h> @interface ListModel : NSObject<NSCoding> @property (nonatomic,copy)NSString *name; @property (nonatomic,copy)NSString *number; + (instancetype)modelWithDict:(NSDictionary *)dict; @end
// // ListModel.m // 通讯录小练习第一波 // // Created by 曹魏 on 2016/11/20. // Copyright © 2016年 itcast. All rights reserved. // #import "ListModel.h" @implementation ListModel + (instancetype)modelWithDict:(NSDictionary *)dict{ ListModel *listModel = [ListModel new]; [listModel setValuesForKeysWithDictionary:dict]; return listModel; } - (void)setValue:(id)value forUndefinedKey:(NSString *)key{} //归档方法 - (void)encodeWithCoder:(NSCoder *)aCoder{ [aCoder encodeObject:_name forKey:@"name"]; [aCoder encodeObject:_number forKey:@"number"]; } //解档方法 - (instancetype)initWithCoder:(NSCoder *)aDecoder{ if (self = [super init]) { self.name = [aDecoder decodeObjectForKey:@"name"]; self.number = [aDecoder decodeObjectForKey:@"number"]; } return self; } @end
注意:由于我归档的是数组,所以很容易理所当然的认为不需要按照自定义对象去对待,即(不需要遵守NSCoding协议,不需要实现归档和解档的两个方法)
但如果不实现会报这样的错误:
2016-11-29 01:24:48.763 通讯录小练习第一波[3396:310882] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ListModel initWithCoder:]: unrecognized selector sent to instance 0x608000221180' (lldb)
原因是:数组中存放的是模型数据,而模型正属于我们自定义对象的一种,所以,我必须在模型对象中遵守协议并实现方法.如上ListModel代码所示.
删除cell操作:一定要记住这个方法
#pragma mark -- 删除cell操作 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{ if (editingStyle == UITableViewCellEditingStyleDelete) { //删除数据 //移除模型中的数据 [self.dataArray removeObjectAtIndex:indexPath.row]; //移除tableview中的列表 [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; //归档 [NSKeyedArchiver archiveRootObject:self.dataArray toFile:KFilePath]; } }
通讯录注销逻辑
列表控制器中有个注销按钮,当我点击注销按钮的时候,登录界面上我想让用户名和密码都为空,自动登录和记住密码switch都关闭,登录按钮也禁用
所以,就有了这个注销逻辑,显然注销要用到逆向数据传递,可以用block,通知,代理,下面用block来做一下:
// // ListTableViewController.h // 通讯录小练习第一波 // // Created by 曹魏 on 2016/11/20. // Copyright © 2016年 itcast. All rights reserved. // #import <UIKit/UIKit.h> @interface ListTableViewController : UITableViewController //BLOCK @property (nonatomic,copy)dispatch_block_t listBlock; @end
#pragma mark -- 点击注销按钮事件 - (void)logOut{ //1.实例化一个alertController UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"确定要注销吗?" message:@"" preferredStyle:UIAlertControllerStyleActionSheet]; //3.添加确定action UIAlertAction *sureAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) { //3.1返回上一个控制器 [self.navigationController popViewControllerAnimated:YES]; if (_listBlock) { _listBlock(); } }];
#pragma mark -- 无论是手动segue还是自动segue都会调用的方法 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ //获取目标控制器 ListTableViewController *listController = segue.destinationViewController; //设置目标控制器的title listController.title = [NSString stringWithFormat:@"%@的通讯录",_nameField.text]; //为block进行赋值操作 listController.listBlock = ^{ //用户名和密码清空 _nameField.text = @""; _passwordField.text = @""; //关闭自动登录和记住密码switch _recordSwitch.on = NO; _autoSwitch.on = NO; //登录按钮 _loginBtn.enabled = NO; //保存在用户偏好设置的数据也要恢复默认值 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:KRecordSwitch]; [[NSUserDefaults standardUserDefaults] setBool:NO forKey:KAutoSwitch]; [[NSUserDefaults standardUserDefaults] setObject:@"" forKey:KUserName]; [[NSUserDefaults standardUserDefaults] setObject:@"" forKey:KPassWord]; }; }