目录:
参考博客:
iOS 沙盒机制
[iOS开发]iOS持久化
iOS面试:数据持久化(八)
iOS 数据持久化 - Sandbox | 8月更文挑战
iOS 沙盒机制及数据存储等操作
沙盒机制
iOS
每个 APP
都有自己的存储空间,这个存储空间叫做沙盒. APP
可以在自己的沙盒中进行数据存取操作,但不能访问其他 App
的沙盒空间.对 App
做一些数据存储或者文件缓存时,一般都保存在沙盒中.
目录结构
沙盒机制根据访问权限和功能区别分为不同的目录: Documents,Library,tmp,SystemData
, 其中library
又包含 Caches
、Preferences
、Application Support
、Cooikes
、SplashBoard
、WebKit
详细介绍如下:
Documents
存放的是app
数据库文件
在iOS11
以后新增了一个文件 APP
,集中管理iOS
上应用内创建的文件,以及各个云盘服务中保存的文件。在iOS
工程info.plist
中设置Application supports iTunes file sharing
和Supports opening documents in place
这两个选项为YES
(默认为NO
),就可以将该应用的沙盒Documents
路径下的文件暴露在文件 APP
中。Library
默认存放设置和其他状态信息。除了caches
子目录之外其他目录都会被iclude
同步Application Support
此目录包含应用程序用来运行但应对用户隐藏的文件,如游戏的新关卡等文件。iTunes
、iCloud
会备份该目录。Caches
保存应用运行时生成的需要持久化的数据,一般存储体积大、不需要备份的非重要数据,如网络请求的音视频与图片等的缓存。
在iOS 5.0
及以后版本中,Caches
当系统磁盘空间非常低时,系统可能会在极少数情况下该删除目录(APP
正在运行时不会发生),所以尽量保证该路径的文件在APP
在重新运行时可以得到重新创建。iTunes、iCloud
不会备份该目录Preference
保存应用的所有偏好设置。如果看过上篇文章,应该就会记得UserDefaults
生成的plist
文件就会保存该目录下。iTunes
、iCloud
会备份该目录SplashBoard
存储启动屏缓存,缓存文件格式为ktx
,本质上就是图片,如果启动屏不生效的问题可以考虑从删除该路径下相关缓存文件这个角度解决WebKit
存储WKWebView
相关的一些数据,如IndexDB
、LocalStorage
、WebSQL
等
tmp
保存应用运行时产生的一些临时数据;应用程序退出、系统空间不够、手机重启等情况下系统都会自动清除该目录的数据。iTunes
、-
不会备份该目录。AppGroup
宿主程序与扩展程序数据共享区域,子目录Library/Preferences
,默认没有该目录,当创建group
的UserDefaults
时会创建该目录,UserDefaults
对应plist
的名称为group
名称SystemData
存放系统数据,无对外暴露的接口
上方提到的文件App
为下方软件(Files
):
举一个我们在文件App
中设置info
之后看到的项目的Documents
路径下的文件(如果配置完info
之后发现文件App
里面没有这个项目的对应文件,那就去先手动往这个项目的沙盒中存入一些字符串等数据,存完之后就可以在文件App
里看到了):
操作方式
获取路径地址:
/// 沙盒主目录
let path = NSHomeDirectory()
/// Documtents目录
let documtentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
/// Library目录
let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first!
/// Application Support目录
let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!
/// Caches目录
let cachesPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
/// tmp目录
let tmpPath = NSTemporaryDirectory()
/**
--------------------------------------------------------
上面形式获取路径返回值为 String 形式,下面形式返回值为 URL 形式
可根据实际情况选择合适的方式
--------------------------------------------------------
*/
let dataURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("sqliteName").appendingPathExtension("sqlite")
/// AppGroup目录路径
let appGroupIdentifier = "group.com.star.LTXiOSUtils.extension"
let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appgroupIdentifier)
比如说我们要获取沙盒根目录,那我们就用(将上方样例中的let
替换成NSString
或URL
就行):
// 获取沙盒根目录路径
NSString *homeDir = NSHomeDirectory();
数据存取
获取到路径后就可以对数据进行存取了,可以直接进行存取操作的数据结构有:
NSMutableArray
、NSArray
NSData
、Data
、NSMutableData
String
、NSString
NSDictionary
、NSMutableDictionary
字符串存储、读取:
- (void)saveString{
//获取Document目录
NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//在沙盒文件下创建文件
NSString *newPath = [string stringByAppendingString:@"/text.txt"];
//要存入的内容
NSString *name = @"UMR";
//写入文件
[name writeToFile:newPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (void)readingString{
//获取路径
NSString *str = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *newPath = [str stringByAppendingString:@"/text.txt"];
//读取数据
NSString *string = [NSString stringWithContentsOfFile:newPath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"%@",string);
}
数组存储、读取:
- (void)saveArray{
//1.获取路径
//获取目录
NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//在沙盒文件下创建文件
NSString *newPath = [string stringByAppendingString:@"/array.plist"];
//要存入的内容
NSArray *array = @[@"zhangsan",@"lisi",@"wangwu"];
//写入
[array writeToFile:newPath atomically:YES];
NSLog(@"%@",newPath);
}
- (void)readingArray{
//获取路径
NSString *str = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *newPath = [str stringByAppendingString:@"/array.plist"];
//读取数据
NSArray *array = [NSArray arrayWithContentsOfFile:newPath];
NSLog(@"%@",array);
}
字典存储、读取:
- (void)saveDictionary{
//获取路径
NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *newPath = [string stringByAppendingString:@"/dictionary.plist"];
//存储内容
NSDictionary *dict = @{@"n1":@[@"1",@"2",@"3"],@"n2":@{@"a":@"d",@"s":@"g"},@"n3":@"uuummmrrr"};
[dict writeToFile:newPath atomically:YES];
NSLog(@"%@",newPath);
}
- (void)readingDictionary{
//获取路径
NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *newPath = [string stringByAppendingString:@"/dictionary.plist"];
//读取内容
NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:newPath];
NSLog(@"%@",dic);
}
data存储、读取:
- (void)saveData{
//获取路径
NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//存储路径
NSString *newPath = [string stringByAppendingString:@"/data.plist"];
//存储内容
NSString *str = @"UMR";
//将string转换成data
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
//写入文件夹
[data writeToFile:newPath atomically:YES];
NSLog(@"%@",newPath);
}
- (void)readingData{
//获取路径
NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//存储路径
NSString *newPath = [string stringByAppendingString:@"/data.plist"];
//从文件读取data
NSData *data = [NSData dataWithContentsOfFile:newPath];
//将data转换成为string
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",str);
}
image
存储 - 需转换为data
进行存储和读取(同样也可以转换为base64编码
然后以字符串的方式进行存储):
- (void)saveImage{
//获取路径
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//在文件路径下面创建该文件夹
NSString *newPath = [path stringByAppendingString:@"/image.png"];
//将.png转为data格式
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"name6.png"]);
// NSData *imageD = UIImageJPEGRepresentation([UIImage imageNamed:@"name6.png"], 1); 如果是JPG格式的,后面的CGFloat为压缩系数;
//将data文件写入文件夹
[imageData writeToFile:newPath atomically:YES];
NSLog(@"%@",newPath);
}
- (void)readingImage{
//获取路径,获取文件
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *newPath = [path stringByAppendingString:@"/image.png"];
NSData *imageData = [NSData dataWithContentsOfFile:newPath];
//将data转为图片格式
UIImage *image = [UIImage imageWithData:imageData];
//获取 - 赋值
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(120, 120, 120, 120)];
imageView.image = image;
[self.view addSubview:imageView];
NSLog(@"%@",newPath);
}
归档与反归档 - 复杂数据的沙盒存储
先创建一个Person
对象,声明相关属性:
//归档必须遵守NSCoding协议
@interface Person : NSObject<NSCoding>
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *gender;
@property (nonatomic,assign) NSInteger age;
@end```
在.m文件
中实现这两个NSCoding
协议方法:
@implementation Person
//对对象进行归档的时候调用
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.gender forKey:@"gender"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
//对对象进行反归档的时候调用
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.gender = [aDecoder decodeObjectForKey:@"gender"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
@end
归档:
- (void)saveData{
Person *person = [[Person alloc] init];
person.name = @"umr";
person.gender = @"m";
person.age = 18;
//进行规档操作
//1.创建一个NSMutableData来存储归档后的数据
NSMutableData *Data = [[NSMutableData alloc] init];
//2.创建归档器
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:Data];
//3.进行归档
[archiver encodeObject:person forKey:@"person"];
//4.归档结束
[archiver finishEncoding];
//获取沙盒路径
NSString *SDPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *newPath = [SDPath stringByAppendingString:@"/archiver.plist"];
//写入文件
[Data writeToFile:newPath atomically:YES];
NSLog(@"%@",newPath);
}
反归档:
- (void)readData{
//获取沙盒路径
NSString *path = [[self documentPath] stringByAppendingString:@"/archiver.plist"];
//获取数据
NSData *data = [NSData dataWithContentsOfFile:path];
NSKeyedUnarchiver *Unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *person1 = [Unarchiver decodeObjectForKey:@"person"];
[Unarchiver finishDecoding];
NSLog(@"%@ %ld %@",person1.name,person1.age,person1.gender);
}
- (NSString *)documentPath{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
对象数组的归档与反归档
- (void)codeArray{
//创建两个person对象
Person *person1 = [[Person alloc] init];
person1.name = @"huahua";
Person *person2 = [[Person alloc] init];
person2.name = @"UMR";
//#存入数组 - 数组中的复杂对象必须遵守NSCoding协议,就可以直接对数组进行归档
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:person1,person2, nil];
//获取路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentPath stringByAppendingPathComponent:@"array.plist"];
NSLog(@"%@",path);
//归档
[NSKeyedArchiver archiveRootObject:array toFile:path];
//反归档
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
// Person *per = arr[0];
NSLog(@"%@",arr);
}
持久化数据存储的方式
XML属性列表(plist)
属性列表是一种XML
格式的文件,拓展名为plist
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber
等类型,就可以使用
writeToFile:atomically:
方法直接将对象写到属性列表文件中
例如我们要存一个字符串进去(上方已经讲过了):
- (void)saveString{
//获取Document目录
NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//在沙盒文件下创建文件
NSString *newPath = [string stringByAppendingString:@"/text.txt"];
//要存入的内容
NSString *name = @"UMR";
//写入文件
[name writeToFile:newPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (void)readingString{
//获取路径
NSString *str = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *newPath = [str stringByAppendingString:@"/text.txt"];
//读取数据
NSString *string = [NSString stringWithContentsOfFile:newPath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"%@",string);
}
我们看一下存入文件App
后的效果:
我们再来看一下程序的运行结果:
Preferences偏好设置(UserDefaults)
很多iOS
应用都支持偏好设置,提供了一套标准的解决方案来为应用加入偏好设置功能,比如保存用户名,字体大小,密码,是否自动登录等。
特点:不会自动删除,iclude\itune
同步,不适合存大数据。即时操作需要注意同步问题,不需要设置路径和文件名
其本身的创建类似于单例模式 我们在后面用不同的属性名再次申请创建,会覆盖之前的数据
我们使用UserDefaults
存放一个账号密码试一下:
- (void)registerUserData {
// 获取偏好设置对象
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//存储数据
[defaults setObject:@"carry" forKey:@"name"];
[defaults setObject:@"123456" forKey:@"password"];
// 同步调用,立刻写到文件中,不写这个方法会异步,有延迟
[defaults synchronize];
}
- (void)judgeUserData {
//需要验证账号密码的地方
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *name = [defaults objectForKey:@"name"];
NSString *password = [defaults objectForKey:@"password"];
NSLog(@"%@ %@", name, password);
}
然后我们按照这个顺序调用:
[self registerUserData];
[self judgeUserData];
我们来看一下打印结果:
NSKeyedArchiver归档
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber
等类型,可以直接用NSKeyedArchiver
进行归档和恢复
特点: 存入Document
,不会自动删除,可存放大型数据,iclude/iTune
同步,存放用户产生的数据,如游戏,操作记录等等。
使用时重要的点就是:
- 遵循
NSCoding
协议 - 实现
encodeWithCoder
和initWithCoder
协议方法,这两个方法是NSCoding
协议所规定的
详细使用案例可见上方方归档与反归档部分的例子讲解。
通过SQlite或CoreData保存在对象数据集
SQlite
就是一个轻量级的数据库
CoreData
是苹果推出的一个数据存储框架,本质上是对SQLite
的一个封装。CoreData
提供了一种对象关系映射(ORM)
的存储关系,类似于Java
的hibernate
框架。CoreData
可以将OC对象
存储到数据库
中,也可以将数据库
中的数据转化为OC对象
,在这个过程中不需要手动编写任何SQL
语句,CoreData
封装了数据库的操作过程,以及数据库
中数据和OC对象
的转换过程。 所以在使用CoreData
的过程中,很多操作就像是对数据库进行操作一样,也有过滤条件、排序等操作。
SQLite
和CoreData
的区别
CoreData
可以在一个对象更新时,其关联的对象也会随着更新,相当于你更新一张表时,其关联的其他表的也回随着更新CoreData
供更简单的性能管理机制,可以限制查询记录的总数,这个类会自动更新其缓存- 多表查询方面,
CoreData
没有SQL
直观,没有类似外连接,左连接等操作
总结一下iOS中数据持久化方案
NSUserDefault
简单数据快速读写Property list
(XML
属性列表)文件存储Archiver
(归档)SQLite
本地数据库CoreData
一些面试题目和iOS持久化
相关的简单问题可见该博客:iOS面试:数据持久化(八)