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];
        
    };
    
    
}

 

转载于:https://www.cnblogs.com/yiyuanchenfeng/p/6104156.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter提供了多种持久化存储数据的方法,包括: 1. Shared Preferences:用于存储键值对,适合存储简单的数据,如用户设置、应用程序配置等。 2. SQLite数据库:用于存储结构化数据,适合存储大量数据和复杂数据,如用户信息、应用程序数据等。 3. 文件存储:用于存储大型进制数据,如图像、音频、视频等。 下面是使用Shared Preferences进行数据持久化的示例代码: ```dart import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { String _name = ''; @override void initState() { super.initState(); _loadName(); } void _loadName() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { _name = prefs.getString('name') ?? ''; }); } void _saveName(String name) async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setString('name', name); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Demo'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Hello, $_name!', style: Theme.of(context).textTheme.headline4, ), TextField( onChanged: (value) => _saveName(value), ), ], ), ), ); } } ``` 在这个示例中,我们使用SharedPreferences来存储用户的名字。在初始化时,我们会调用_loadName()方法来加载存储在SharedPreferences中的名字。在用户输入名字后,我们会调用_saveName()方法来保存名字。每当名字发生变化时,UI会自动更新。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值