我把数据持久化分成下面这些部分来讨论:
- NSUserDefault
- 文件存储 (包括:Plist属性列表, archive归档, 文件流)
- keychain 钥匙串
- 数据库 (包括: SQLite, Core Data 和其他第三方方法)
NSUserDefault
app的全局单例,用于永久保存数据.
他支持的数据类型有:NSNumber,NSString,NSDictionary,NSArray,NSDate,BOOL
他的数据是通过key-value键值对一一对应保存的.
iOS会自动吧字典中的键值对转换成对应的XML, 也就plist文件, 这个文件会被保存到APP的沙盒目录中(路径为Library/Preference/plist文件名)
其实NSUserDefaults就是iOS为我们封装的对plist文件的常用操作(其实可以归类到"文件储存"的"plist属性列表"里面,,但是二者之间还是有一些区别..我们分开来讲),我们可以用来保存一些简单的数据,,比如系统配置,用户设置的参数等等....
具体代码:
//存入数据
[[NSUserDefaults standardUserDefaults] setObject:@"woshishuju" forKey:@"woshikey"];
//取出数据
NSString *str = [[NSUserDefaults standardUserDefaults] objectForKey:@"woshikey"];
//删除数据
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"woshikey"];
//操作完成后需要同步数据,否则偶然会因为别的线程占大量内存而没有报错成功
[defaults synchronize];
文件存储
在了解具体的文件储存机制之前...我们先来了解一下iOS的沙盒机制
沙盒机制
在iOS中,应用程序只能在为该程序创建的文件系统中读取文件, 不可以去其他地方访问,,这个区域就叫做沙盒(Sandbox)....
沙盒的目录结构
- Document:
保存应用运行时生成需要持久化的数据.iTunes会自动备份该目录. - Library:
Library/Caches:
一般存储的时候缓存文件,例如图片,视频等.此目录下的文件不会在应用退出时删除,itunes备份的时候不会备份该目录.
Library/Preferences:
保存应用程序的偏好设置,我们不应该在这里直接创建文件,而是通过NSUserDefault来访问应用偏好.iTunes会自动备份该目录. - tmp:
临时文件目录,在程序重新运行和开机时,会清空tmp文件夹.
值得一提的,还有应用程序的包文件.app
在iOS 8之前.app文件也放在沙盒文件夹里...在iOS 8之后,有单独的文件夹存储所有的程序包....但是我们也可以获取到他的路径..待会我们可以通过打印来看到.
获取文件路径
- Documents
/**
* 获取Document下的文件路径
*
* @param NSDocumentDirectory 获取Document目录
* @param NSUserDomainMask 是在当前沙盒范围内查找
* @param YES 展开路径,NO是不展开
*
* @return test.txt文件的路径
*/
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES)firstObject]stringByAppendingPathComponent:@"test.txt"];
打印出来:
/var/mobile/Containers/Data/Application/这里是UUID隐藏掉/Documents/text.txt
Library
获取方法与Documents类似,只需要把相应的改为NSLibraryDirectory即可.
而caches和preferences只需要在其后加上stringByAppendingPathComponent:即可..
当然...Preferences是由系统维护的,,,我们不需要手动获取这个路径(虽然能获取到).只需要借助NSUserDefault来操作即可.tmp
/**
* 获取tmp文件目录下的文件路径
*
* @return test.txt的文件路径
*/
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.txt"];
打印出来:
/private/var/mobile/Containers/Data/Application/这里是UUID隐藏掉/tmp/test.txt
- 获取应用程序包.app的路径
NSString *path = [[NSBundle mainBundle] resourcePath];
打印出来
/var/containers/Bundle/Application/和上面不一样的UUID/test3.app
这里可以看到.app的存放路径和沙盒文件的路径完全不一样....而且同一个的应用连UUID都不一样了...
- 获取程序包中某一个图片资源的路径
NSString *imagePath = [[NSBundle mainBundle]pathForResource:@"apple" ofType:@"png"];
1.属性列表
a.NSUsrDefault
NSUserDefault为我们自动创建了plist文件..在沙盒的preference文件夹内.
b.手动创建plist
通过开发者在外部手动创建plist文件...这时plist文件在程序包bundle的资源中.....值得注意的是...这时的plist是不可写...无法把数据写进plist文件.
所以,如果想要对数据进行写操作,我们需要把plist文件拷贝一份到沙盒的Documents中,这样才能进行文件写入的操作
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:@"List.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:documentPath])
{
NSString *defaultPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"List.plist"];
NSError *error;
BOOL success = [fileManager copyItemAtPath:defaultPath toPath:documentPath error:&error];
NSAssert(success,@"写入了错误的文件");
}
然后可以通过取出数据,,修改,,再写入文件,,的方法来进行增删改查
//比如里面存的是数组,可以这么取
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:path]
//修改后
...
//写入文件
[array writeToFile:path atomically:YES];
c.通过代码创建plist文件
我们也可以直接用代码在document中建立一个plist.然后用writeToFile把代码写进去
NSString *myFilePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:@"List.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:myFilePath] == NO) {
[fileManager createFileAtPath:myFilePath contents:nil attributes:nil];
}
2.archive归档
对象归档是一种序列化方式, 为了便于数据传输,, 先将归类对象序列化为一个文件, 然后再通过反归档将数据恢复到对象中.
实现归档有一个条件:
该对象的类必须实现NSCoding协议,而且每个成员变量应该是基本数据类型或者都是实现了NSCoding协议的某个类的实例.
不难发现,archive比起plist的优点是,可以持久化任意符合条件的对象.
具体过程如下:
我们先创建一个代理了NSCoding的类.
//.h
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCoding>
@property (nonatomic, retain) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, retain) NSData *data;
@end
//.m
#import "Person.h"
@implementation Person
//编码成可以持久化的格式,归档时调用
-(void)encodeWithCoder:(NSCoder *)aCoder
{
//对每个属性都要进行重新编码
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
[aCoder encodeObject:self.data forKey:@"data"];
}
//解码过程,反归档时调用
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
//解码的key要和编码时给的key一致
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
self.data = [aDecoder decodeObjectForKey:@"data"];
}
return self;
}
@end
进行编码,然后归档:
把Person类的实例对象张三通过archiver转码,转化为一个data对象.然后把data对象存入到沙盒Document下的Data.archieve文件中.
Person *person = [[Person alloc] init];
person.name = @"张三";
person.age = 18;
//把image转化成data传进去,因为data是可以coding的
person.data = UIImagePNGRepresentation([UIImage imageNamed:@"zhangsan.png"]);
//创建一个可变data用于存放归档出来的数据
NSMutableData *data = [NSMutableData data];
//创建一个归档对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
//进行归档编码
[archiver encodeObject:person forKey:@"MyKey"];
[archiver finishEncoding];
NSString *dataPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"Data.archive"];
//把可变data写进归档的.archive文件里
[data writeToFile:dataPath atomically:YES];
接下来,我们试试吧这个归类的对象取出来...就是所谓解码
NSString *dataPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"Data.archive"];
NSData *data = [NSData dataWithContentsOfFile:dataPath];
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *model = [unArchiver decodeObjectForKey:@"MyKey"];
[unArchiver finishDecoding];
UIImage *image = [UIImage imageWithData:model.data];
NSLog(@"%@",model.name);
NSLog(@"%d",(int)model.age);
还是很好理解的...
3.文件流
文件流:把文件分块慢慢传过去,像流水一样.所以文件存储和读取都不是一下子运行的,而是一段一段加载的. 例如:网络视频下载的进度
这一部分我们放到后面的网络部分去讲...
KeyChain钥匙串
在iOS开发中常见的保存用户信息的方法归档或者NSUserDefault,实际上是不安全的(放在沙盒里面...可以很容易被取出来)....像密码,证书等等,需要更为安全的keychain...
keychain里保存的信息不会因为App被删除而丢失,在用户重新安装app后依然有效,数据还在.
keychain服务提供一种安全的保存私密信息的方式.每个程序都有独立的keychain存储....从ios3.0之后,跨程序分享keychain变得可行...keychain可以用来保存WiFi密码和vpn凭证等等
它实际上是一个数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的...
这里把概念放在这里...具体实现之后总结.....
(其实实际开发中,这个东西还挺有用...我在做手游SDK的时候,,,老板要求用户卸载游戏后,,,再次安装app时还是能得到登录过的账号自动登录......之前想用UUID来做...却发现实际上每次重装UUID都会变化...这里就可以使用keychain来完成)
数据库
1.SQLite
首先需要导入libsqlite3.0.tbd静态库
a.创建或打开数据库
首先,创建一个数据库到沙盒的Document中,然后打开他.
核心代码 sqlite3_open([strPath UTF8String], &db)
#import <sqlite3.h>
static sqlite3 *db;
- (void)openSqlite {
//判断数据库是否为空,如果不为空说明已经打开
if(db != nil) {
NSLog(@"数据库已经打开");
return;
}
//获取文件路径
NSString *strPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"my.sqlite"];
//打开数据库
//如果数据库存在就打开,如果不存在就创建一个再打开
int result = sqlite3_open([strPath UTF8String], &db);
//判断
if (result == SQLITE_OK) {
NSLog(@"数据库打开成功");
} else {
NSLog(@"数据库打开失败");
}
}
b.创建表格
- (void)createTable {
//sqlite语句
NSString *sqlite = [NSString stringWithFormat:@"create table if not exists 'student' ('number' integer primary key autoincrement not null,'name' text,'sex' text,'age'integer)"];
//2.执行
char *error = NULL;
int result = sqlite3_exec(db, [sqlite UTF8String], nil, nil, &error);
if (result == SQLITE_OK) {
NSLog(@"创建表成功");
} else {
NSLog(@"创建表失败");
}
}
c.增
以下部分雷同的代码,省略掉了,只留下最关键的代码
- (void)addStudent:(student *)stu {
NSString *sqlite = [NSString stringWithFormat:@"insert into student(number,name,age,sex) values ('%ld','%@','%@','%ld')",stu.number,stu.name,stu.sex,stu.age];
char *error = NULL;
int result = sqlite3_exec(db, [sqlite UTF8String], nil, nil, &error);
}
d.删
- (void)delete:(student*)stu {
NSString *sqlite = [NSString stringWithFormat:@"delete from student where number = '%ld'",stu.number];
char *error = NULL;
int result = sqlite3_exec(db, [sqlite UTF8String], nil, nil, &error);
}
c.改
- (void)updataWithStu:(student *)stu {
NSString *sqlite = [NSString stringWithFormat:@"update student set name = '%@',sex = '%@',age = '%ld' where number = '%ld'",stu.name,stu.sex,stu.age,stu.number];
char *error = NULL;
int result = sqlite3_exec(db, [sqlite UTF8String], nil, nil, &error);
}
e.查
- (NSMutableArray*)selectWithStu {
NSMutableArray *array = [[NSMutableArray alloc] init];
NSString *sqlite = [NSString stringWithFormat:@"select * from student"];
sqlite3_stmt *stmt = NULL;
//3.预执行sqlite语句
int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
if (result == SQLITE_OK) {
NSLog(@"查询成功");
while (sqlite3_step(stmt) == SQLITE_ROW) {
student *stu = [[student alloc] init];
stu.number = sqlite3_column_int(stmt, 0);
stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)] ;
stu.sex = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 2)] ;
stu.age = sqlite3_column_int(stmt, 3);
[array addObject:stu];
}
} else {
NSLog(@"查询失败");
}
sqlite3_finalize(stmt);
return array;
}
f.关闭数据库
- (void)closeSqlite {
int result = sqlite3_close(db);
}
需要用到SQL语言,而且查询部分也挺麻烦的...实际开发中会用三方库更多吧...
2.Core Data
CoreData 是 Cocoa 平台上用来管理模型层数据和数据持久化的一个框架,说简单点,就是一个数据库存储框架
目录
1.数据模型和coredata栈的建立
2.增删改查
3.关联表的创建
4.迸发操作
5.CoreData和tableView的结合
6.模型版本和数据迁移
3.三方数据库
常用的有FMDB