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

本文详细介绍了iOS中数据持久化的五种方式:NSUserDefaults、归档、文件(plist和txt)、数据库(包括FMDB和CoreData)以及KeyChain。强调了各种方式在保存数据量、安全性和适用场景上的差异,特别提到KeyChain的单向加密安全性,并给出了数据库操作中的事务处理和FMDB的使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

 

1.NSUserDefaults:保存设置数据

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

3.文件(plist,txt)

4.数据库和CoreData

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

 

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

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

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

 

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

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

 

二、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,记录上次的选中状态。

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

(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

  `

四、文件保存  writeToFile:

1、plist 文件

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

(2)plist 文件 属于 xml 文件

2、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(@"写入成功");
    }

 

五、数据库和CoreData

 

1、基本sql语句(稍后另作随笔添加)

 

2、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的二次封装

 

3、CoreData

(1)基本操作

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

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

六、KeyChain

 

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

转载于:https://www.cnblogs.com/chenyuansheng/p/5293100.html

第十三章 文件 对数据的管理无论是用数组还是链表,都是存储在内存中的,程序结束后都会丢失,下一次运行程序时,要重新输入或运算生成数据。要把程序运行的数据保存起来以便下次运行继续使用,在计算机中持久保存数据方式是利用文件保存。 13.1 文件概述 文件一般是指存储在外部介质上数据的集合。文件以数据形式存放在外部介质上,操作系统以文件为单位对数据进行管理。想找到存在外部介质上的数据,必须按文件名找到指定的文件然后再从该文件中读取数据。要想在外部介质上存储数据必须先建立一个文件(以文件名标识),才能向它输出数据。从操作系统角度,每一个与主机相连的输入输出设备都可以被看作一个文件。在程序运行时,常常需要将一些数据(运行的最终结果或中间数据)输出到磁盘上保存,以后要用时再从磁盘中输入到计算机的内存,这就要用到磁盘文件。 操作系统的文件标识包括三部分: (1)文件路径:表示文件在外部存储设备中的位置。 (2)文件名:遵循标识符的命名规则。 (3)文件扩展名:表示文件的性质(.txt .dat .c)。 如:d:\c++\temp\flie1.dat 文件操作是一种典型的IO操作(输入输出操作)。输入输出是针对内存而言的,进内存为输入,出内存为输出。 标准输入输出就是标准输入设备(键盘)和标准输出设备(显示器),键盘和显示器就是一种文件。C语言将文件看成字符(字节)的序列,即由一个一个字符(字节)的数据顺序组成。C语言中对文件的存取是以字符(字节)为单位的,输入输出数据流的开始和结束仅受程序控制不受物理符号控制(如回车换行符)。输出时不会自动增加回车换行符作为记录结束的标志,输入时不以回车换行符作为记录的间隔(实际上C文件不是由记录构成的),这种文件称为流式文件。 ANSI新标准文件采用缓冲方式,系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。从内存向磁盘输出数据必先送到输出缓冲区,装满缓冲区后才一起送到磁盘。从磁盘向内存输入数据先送到输入缓冲区,程序需要数据时去缓冲区读取,若缓冲区无数据,则程序进入阻塞状态(等待数据)。 C语言把数据看作是一连串的字符(字节),根据数据的组成形式,分为ASCII文件和二进制文件。 文本文件又称ASCII文件,每个字节存放一个ASCII码,代表一个字符。如1、2的ASCII码分别为49、50,所以整数12用ASCII文件存放时,存放形式为00110001 00110010 二进制文件是直接用数据的二进制形式存放的,即把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。 程序中实现对文件的处理通常分三步: (1)打开文件:将程序与文件建立联系。 (2)操作文件:对文件进行读写操作,即输入输出。 (3)关闭文件:操作完成应当切断文件与程序的联系。 C语言中没有输入输出语句,对文件的读写都是用库函数实现的。ANSI规定了标准输入输出函数,用它们对文件进行读写,这些函数的声明包含在头文件stdio.h中。 13.2 文件的打开与关闭 文件进行读写操作前先要打开,使用完毕要关闭。打开文件,是建立文件的各种有关信息,并使文件指针指向该文件,以便进行其他操作。关闭文件则断开指针与文件之间的联系。 1.打开文件(fopen函数) 原型:FILE *fopen(char *filename, char *mode); FILE是在stdio.h头文件中定义的一个结构体,用来保存文件信息。 fopen函数用来打开一个文件,其调用的一般形式为: 文件指针名=fopen(文件名,使用文件方式); 其中: ·“文件指针名”必须是被说明为FILE类型的指针变量。 ·“文件名”是被打开文件的文件名,是字符串常量或字符串数组。 ·“使用文件方式”是指文件的类型和操作要求。 如: FILE *fp; fp=fopen("file1.txt","r"); 其意义是在当前目录下打开文件file1.txt,只允许读操作,并使fp指向该文件。 又如: FILE *fp; fp=fopen("c:\\file2.txt","rb"); 意义是打开C驱动器磁盘根目录下的文件file2.txt,对其按二进制方式进行读操作。两个反斜线\\中的第一个表示转义字符,第二个表示根目录。 文件打开方式 r 只读打开一个文本文件,只允许读数据 w 只写打开或建立一个文本文件,只允许写数据 a 追加打开一个文本文件,并在文件末尾写数据 r+ 读写打开一个文本文件,允许读和写 w+ 读写打开或建立一个文本文件,允许读和写 a+ 读写打开一个文本文件,允许读和在文件末尾写数据 rb 只读打开一个二进制文件,只允许读数据 wb 只写打开或建立一个二进制文件,只允许写数据 ab 追加打开一个二进制文件,并在文件末尾写数据 rb+ 读写打开一个二进制文件,允许读和写 wb+ 读写打开或建立一个二进制文件,允许读和写 ab+ 读写打开一个二进制文件,允许读和在文件末尾写数据 (1)文件使用方式由r、w、a、b、+拼成,含义分别为: r(read) 读 w(write) 写 a(append) 追加 b(binary)二进制文件 + 读和写 (2)用r方式打开文件时文件必须存在,只能从该文件读出。 (3)用w方式打开只能向文件写入。若打开的文件不存在,则以指定的文件名建立新文件。若存在,则将存在的文件删除,重建新文件。 (4)向一个已存在的文件追加新的信息,只能用a方式打开。文件必须存在,否则会出错。 (5)打开文件出错时fopen将返回一个空指针值NULL。程序中可用这一信息判断是否完成打开文件的工作,并做相应的处理。因此常用以下程序段打开文件: if((fp=fopen("c:\\file2.txt","rb"))==NULL) { printf("\nerror on open c:\\file2.txt!"); exit(0); } (6)把文本文件读入内存时,要将ASCII码转换成二进制码。把内存数据以文本方式写入磁盘时,要将二进制码转换成ASCII码。因此文本文件的读写花费较多的转换时间,对二进制文件的读写不存在这些转换。 (7)标准输入文件(键盘)、标准输出文件(显示器)、标准出错输出(出错信息)是由系统打开的,可直接使用。 2.关闭文件(fclose函数) 文件使用完毕,应用关闭文件函数把文件关闭,从而把缓冲区的数据写入文件中。否则程序结束时可能造成数据丢失。 fclose函数使用形式: fclose(文件指针); 如:fclose(fp); 正常完成关闭文件操作时,fclose函数返回值0。发生错误时返回EOF。 13.3 文件读写 对文件的读和写是最常用的文件操作,在C语言标准库中提供了多种文件读写的函数。 字符读写函数:fgetc和fputc 字符串读写函数:fgets和fputs 数据块读写函数:fread和fwrite 格式化读写函数:fscanf和fprintf 13.3.1 字符读写函数 1.读字符函数fgetc 规格:int fgetc(FILE * stream); 功能:从指定的文件中读一个字符。 参数:stream为指向文件的指针。 返回值:从stream所指的文件流中读取一个字符,转换为int类型返回。若已到文件尾返回EOF,文件状态改为结束状态。若读错误返回EOF,文件改为错误状态。EOF在stdio.h中定义为-1。 例如: ch=fgetc(fp); 其含义是从打开的文件fp中读取一个字符并送入ch中。 对于fgetc函数的使用有几点说明: (1)在fgetc函数调用中,读取的文件必须是以读或读写方式打开的。 (2)文件内部有一个位置指针,用来指向文件的当前读写字节,文件打开时,位置指针总指向文件的第一个字节。使用fgetc函数后位置指针向后移动一个字节。因此可连续多次使用fgetc函数读取多个字符。文件指针是指向整个文件的,要在程序中定义说明,只要不重新赋值文件指针值不变。文件内部的位置指针用于指示文件内部的当前读写位置,每读写一次,位置指针向后移动,它无需在程序中定义说明,由系统自动设置。 例:读取文本文件file.txt,把其中所有非空格字符输出在标准输出设备上。 #include #include #include int main() { FILE *fp; char ch; if((fp=fopen("file.txt","r"))==NULL) { printf("\nCan't open file,strike any key exit!"); getch(); /*等待敲键盘,显示Can't open file,strike any key exit!*/ exit(1); /*结束程序*/ } ch=fgetc(fp); while(ch!=EOF) /*文件结束时读取得到EOF*/ { if(ch!=' ') putchar(ch); ch=fgetc(fp); } fclose(fp); return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值