数据保存(永久保存)方式

1、数据保存(永久保存)方式有五种:html

 

1.NSUserDefaults:保存设置数据算法

2.归档:保存自定义数据sql

3.文件(plist,txt)数据库

4.数据库和CoreData数组

5.KeyChain(钥匙串—系统中 钥匙串访问 这个程序)安全

 

只有数据库和CoreData才适合用于保存大量的数据(效率高,由于有数据库的算法),其它方式只用于保存少许数据(保存大量数据效率低)。多线程

前四个在沙盒范围内的,第五个即便删除app也存在于手机中。app

KeyChain钥匙串是单向加密的,最为安全。异步

 

数据保存能够提高app的体验度:一开始进入app,显示的是上次关闭时的数据;当刷新时才请求新的数据atom

数据保存多数用于实现`收藏`功能

 

2、NSUserDefaults   

(1)只能保存基本数据类型(int,float,double,BOOL,NSString,NSArray,NSDictionary,NSData,NSURL等),不能保存自定义的对象

(2)用于保存系统的设置型数据,如手机操做系统里面的`通用`设置数据

(3)NSUserDefaults 系统采起按期保存的机制,具体多久保存一次未知,也可手动采起当即保存(强制保存)(须要调用synchronize)

(4)只用于保存少许数据

(5)以键值对的形式保存

  基本操做:

    //set方法保存(按期保存,若是当即保存,须要调用synchronize)
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"key1"];
    [[NSUserDefaults standardUserDefaults] setObject:@[] forKey:@"key2"];//数组

    //强制保存
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    //取值
    [[NSUserDefaults standardUserDefaults] boolForKey:@"key"];
    
    //删除
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"key"];

  应用例子:自动登陆

#import "ViewController.h"

//自动登陆
//#define kAutoLoginKey @"kAutoLoginKey"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *username;
@property (weak, nonatomic) IBOutlet UITextField *password;
- (IBAction)login:(id)sender;
- (IBAction)autoLoginBtnClick:(UIButton *)sender;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //自动登陆
    if ([[NSUserDefaults standardUserDefaults] boolForKey:@"kAutoLoginKey"])
    {
        //发送登陆请求,而后请求主页界面的数据
        NSLog(@"发送登陆请求");
    }
    else
    {
        //弹出登陆界面,登陆成功后dismiss掉登陆界面
        NSLog(@"弹出登陆");
    }
}
- (IBAction)login:(id)sender {
    //若勾选了自动登陆按钮,保存用户信息到 NSUserDefaults
}
//自动登陆按钮
- (IBAction)autoLoginBtnClick:(UIButton *)sender
{
    //修改按钮状态
    sender.selected = !sender.selected;   
    //保存当前状态
    [[NSUserDefaults standardUserDefaults] setBool:sender.selected forKey:@"kAutoLoginKey"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
@end

    

   拓展应用:重启app,记录上次的选中状态。

3、归档(序列化)与解归档(反序列化)

(1)能保存自定义的对象,解决NSUserDefaults不能解决的问题

(2)转化成NSData,以键值对的形式保存:要保存的对象(类),被转化为NSData,保存到NSUserDefaults下

(3)归档和反归档须要`被归档的类`遵照NSCoding协议,自定义的类须要重写`encodeWithCoder:`和`initWithCoder:`方法

 

#import <Foundation/Foundation.h>
//归档须要遵照NSCoding协议
@interface Person : NSObject<NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSInteger age;
+ (instancetype)personWithName:(NSString *)name age:(int)age;
@end

#import "Person.h"
@implementation Person
+ (instancetype)personWithName:(NSString *)name age:(int)age
{
    Person *person = [[self alloc] init];
    person.name = name;
    person.age = age;
    return person;
}
//归档的时候会调用,也就是调用encodeObject:forKey:
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    //保存
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeObject:@(self.age) forKey:@"age"];
    //encodeObject:(id类型),所以int类型没法保存
}
//解归档的时候会调用,也就是调用decodeObjectForKey:
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init])
    {
        //取值
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [[aDecoder decodeObjectForKey:@"age"] integerValue];
    }
    return self;
}
@end

#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; [self encode]; [self decode]; } /** * 归档 */ - (void)encode { Person *p1= [Person personWithName:@"小明" age:20]; Person *p2 = [Person personWithName:@"小张" age:30]; NSArray *array = @[p1,p2]; //保存对象转化为二进制数据(必定是可变对象) NSMutableData *data = [NSMutableData data]; //1.初始化 NSKeyedArchiver *archivier = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; //2.归档 [archivier encodeObject:array forKey:@"key"]; //3.完成归档 --- 必须调用,不然报警告,且没法完成归档 [archivier finishEncoding]; //4.保存 [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"data"]; //保存到指定目录: //[data writeToFile:保存路径 atomically:YES]; /* atomically: 为YES,表示先写入到临时目录,确认整个文件保存过程当中没出错后,再自动写入到指定的目录 为NO,表示直接写入到指定的目录,就算出错也继续写入 所以推荐 atomically:YES */
NSLog(@"%@",data); } /** * 解归档 */ - (void)decode { //获取保存的数据 NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"data"]; //1.初始化 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; //2.解归档 NSArray *persons = [unarchiver decodeObjectForKey:@"key"]; //3.完成解归档 [unarchiver finishDecoding]; for (Person *person in persons) { NSLog(@"%@ - %ld",person.name,(long)person.age); } } @end

  `

4、文件保存  writeToFile:

一、plist 文件

(1)建立方式:直接建立、代码建立:把数据保存到plist文件中

(2)plist 文件 属于 xml 文件

二、txt 文件

NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
#if 0
    BOOL isOK = [@"111" writeToFile:[cachePath stringByAppendingPathComponent:@"1.txt"] atomically:YES encoding:NSUTF8StringEncoding error:nil];
#endif
    NSArray *array = @[@"111",@"222",@{@"key1":@"value1",@"key2":@[@"333",@"444"]}];
    
    BOOL isOK = [array writeToFile:[cachePath stringByAppendingPathComponent:@"1.plist"] atomically:YES];
    
    if (!isOK)
    {
        NSLog(@"写入失败");
    }
    else
    {
        NSLog(@"写入成功");
    }

 

5、数据库和CoreData

 

一、基本sql语句(稍后另做随笔添加)

 

二、fmdb 第三方库

(1)线程不安全的

  (直接调用 FMDatabase 对象,执行读写等操做)

    //获取Document路径
    NSURL *url = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0];
    //把url对象转换成string
    NSLog(@"%@",[url absoluteString]);//本地路径
    NSLog(@"%@",[url relativePath]);//相对路径
    //设置建立数据库的路径(包行数据库文件的命名)
    NSString *path = [[url relativePath] stringByAppendingPathComponent:@"user.db"];

/****************  线程不安全  ****************/
    //初始化database对象
    FMDatabase *database = [FMDatabase databaseWithPath:path];
    
    //建立并打开/打开数据库:若是数据库不存在,就建立而且打开;不然直接打开。
    [database open];
    
    //除了查询(select)是query操做以外,其余的都是update操做
//    [database executeUpdate:<#(NSString *), ...#>];
//    [database executeQuery:<#(NSString *), ...#>];
    
    //关闭数据库
    [database close];

 

(2)线程安全的

  (调用 FMDatabaseQueue 对象来执行数据库操做)

  咱们知道直接使用libsqlite3进行数据库操做实际上是线程不安全的,若是遇到多个线程同时操做一个表的时候可能会发生意想不到的结果。为了解决这个问题建议在多线程中使用FMDatabaseQueue对象,相比FMDatabase而言,它是线程安全的。

  保证每一次读写等操做都不是异步的,而是同步的;同时有两个线程进入该操做时,按顺序执行。

/****************  线程安全  ****************/
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
    //执行sql语句 (线程安全的数据库不须要打开,inDatabase内含打开数据库的代码)
    [queue inDatabase:^(FMDatabase *db) {
        //执行增删改查
        
        NSString *sql = @"select * from xx";
        
        [db executeQuery:sql];
        [db executeUpdate:@""];
    }];

   注意:dataWithPath中的路径参数通常会选择保存到沙箱中的Documents目录中;若是这个参数设置为nil则数据库会在内存中建立;若是设置为@””则会在沙箱中的临时目录建立,应用程序关闭则文件删除。

(3)两个基本方法

  FMDatabase类提供了两个方法`executeUpdate:`和`executeQuery:`分别用于执行无返回结果的查询和有返回结果的查询。须要指出的是,若是调用有格式化参数的sql语句时,格式化符号使用“?”而不是“%@”、等。下面是两种状况的代码片断:

a.无返回结果

-(void)executeNonQuery:(NSString *)sql{
    //执行更新sql语句,用于插入、修改、删除
    if (![self.database executeUpdate:sql]) {
        NSLog(@"执行SQL语句过程当中发生错误!");
    }
}

 

b.有返回结果

-(NSArray *)executeQuery:(NSString *)sql{
    NSMutableArray *array=[NSMutableArray array];
    //执行查询sql语句
    FMResultSet *result= [self.database executeQuery:sql];
    while (result.next) {
        NSMutableDictionary *dic=[NSMutableDictionary dictionary];
        for (int i=0; i<result.columnCount; ++i) {
            dic[[result columnNameForIndex:i]]=[result stringForColumnIndex:i];
        }
        [array addObject:dic];
    }
    return array;
}

  对于有返回结果的查询而言,查询完返回一个游标FMResultSet,经过遍历游标进行查询。并且FMDB中提供了大量intForColumn、stringForColumn等方法进行取值。

(3)事务

  用于多线程多个同时操做时,保证操做全都成功,不然全不成功。

  不是只有FMDB才支持事务,sql自己就支持事务,FMDB将其封装成了几个方法来调用,不用本身写对应的sql。其实在在使用libsqlite3操做数据库时也是原生支持事务的(由于这里的事务是基于数据库的,FMDB仍是使用的SQLite数据库),只要在执行sql语句前加上“begin transaction;”执行完以后执行“commit transaction;”或者“rollback transaction;”进行提交或回滚便可。另外在Core Data中你们也能够发现,全部的增、删、改操做以后必须调用上下文的保存方法,其实自己就提供了事务的支持,只要不调用保存方法,以前全部的操做是不会提交的。

  在FMDB中FMDatabase有beginTransaction、commit、rollback三个方法进行开启事务、提交事务和回滚事务。

    //事务:要么操做都成功,要么都不成功。
    FMDatabase *database = [FMDatabase databaseWithPath:path];
    
    //打开数据库:若是数据库不存在,就建立而且打开。不然直接打开。
    [database open];
    
    //建立表
    NSString *sql = @"create table if not exists User (id integer primary key autoincrement, name text)";
    //执行sql
    [database executeUpdate:sql];
    
    sql = @"insert into User (name) values (?)";
    
    //添加事务
    [database beginTransaction];
    
    //插入10条数据
    for (int i = 0; i < 10; i++)
    {
        BOOL isOK = [database executeUpdate:sql,[NSString stringWithFormat:@"test%d",i+1]];
        
        //在这里模拟失败
        if (i == 5)
        {
            //操做失败,回滚数据库:取消本次操做,把数据库回滚到`添加事务前`的状态
            [database rollback];
            return;
        }
        
        if (isOK)
        {
            NSLog(@"插入成功");
        }
        else
        {
            NSLog(@"插入失败");
            //正常的程序设计,都会在插入失败这里设置回滚
            [database rollback];
            return;
        }
    }
    
    NSLog(@"%@",path);
    
    //提交事务
    [database commit];
    
    //运行结果:插入数据成功,但数据库中没有数据 --- 事务。

  事务模拟转帐:

    //添加事务
    [database beginTransaction];
//转帐 BOOL isOK = [database executeUpdate:sql,[NSString stringWithFormat:@"test%d",i+1]]; if (!isOK) { [database rollback]; return; }
//收帐 isOK = [database executeUpdate:sql,[NSString stringWithFormat:@"test%d",i+1]]; if (!isOK) { [database rollback]; return; } [database commit];

 

(4)fmdb的二次封装

 

三、CoreData

(1)基本操做

(2)实体和实体之间的关系

(3)版本升级和数据迁移

6、KeyChain

 

()以键值对的形式保存数据