一、数据持久化概述
数据持久化就是数据的永久存储。其本质是将数据保存为文件,存到程序的沙盒中。
1、数据持久化的方式
1.1 writeToFile:简单对象写入文件
1.2 NSUserDefaults:应用程序偏好设置
1.3 Sqlite:轻量级关系型数据库,不能直接存储对象(NSData除外),需要用到一些SQL语句,先将复杂对象归档(对象->NSData)
1.4 CoreData:对象型数据库,实质是将数据库的内部存储细节封装
1.5 Plist文件
2、应用程序沙盒
每一应用程序都有自己的应用沙盒,沙盒的本质就是一个文件夹,名字是随机分配的。
与其他应用程序沙盒隔离,应用程序本身只能访问自己沙盒的数据。(iOS8+对沙盒之间的访问部分开放)
2.1 应用程序包(.app)
包含了应用程序中所用到的所有资源文件和可执行文件(Base on Unix)。iOS8时,app不存储在沙盒中,有单独的文件夹存储所有程序的app包。
2.2 HomeDirectory
Documents:保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录
tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录
Library/Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据
Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录
2.3 获取沙盒路径
沙盒根目录: NSHomeDirectory();
沙盒临时目录:NSTemporaryDirectory();
Library/Preferences:NSUserDefaults
//1.获取沙盒中Documents文件夹的路径
//第一种方式:
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; //NO, path = @"~/"(相对路径); YES 绝对路径
NSLog(@"%@", documentPath);
//第二种方式: (不建议采用,因为新版本的操作系统可能会修改目录名)
NSString *homePath = NSHomeDirectory();
NSString *documentPath2 = [homePath stringByAppendingPathComponent:@"library/caches"];
NSLog(@"%@", documentPath2);
//2.获取应用程序包路径(.app)
NSLog(@"%@", [NSBundle mainBundle].resourcePath);
二、简单对象持久化
1、简单对象
NSString\NSArray\NSDictionary\NSData
使用writeToFile:方法,将数据存储为.plist文件
atomically参数为是否写入缓存
//字符串
NSString *string = @"I U";
//数组
NSArray *array = @[@"张三", @"李四", @"王五"];
//字典
NSDictionary *dictionary = @{@"name":@"张三", @"age":@"20", @"sex":@"男"};
//NSData
UIImage *image = [UIImage imageNamed:@"1.jpg"];
NSData *data = UIImageJPEGRepresentation(image, 1);
//1.拼接存储路径
NSString *strPath = [documentPath stringByAppendingPathComponent:@"string.txt"];
NSString *arrayPath = [documentPath stringByAppendingPathComponent:@"array.txt"];
NSString *dicPath = [documentPath stringByAppendingPathComponent:@"dict.txt"];
NSString *dataPath = [documentPath stringByAppendingPathComponent:@"data.txt"];
//2.写入文件
[string writeToFile:strPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
[array writeToFile:arrayPath atomically:YES];
[dictionary writeToFile:dicPath atomically:YES];
[data writeToFile:dataPath atomically:YES];
//3.读取文件内容
NSString *fileString = [NSString stringWithContentsOfFile:strPath encoding:NSUTF8StringEncoding error:nil];
NSArray *fileArray = [NSArray arrayWithContentsOfFile:arrayPath];
NSDictionary *fileDict = [NSDictionary dictionaryWithContentsOfFile:dicPath];
NSData *fileData = [NSData dataWithContentsOfFile:dataPath];
NSLog(@"%@", fileString);
NSLog(@"%@", fileArray);
NSLog(@"%@", fileDict);
NSLog(@"%@", fileData);
2、文件管理类:NSFileManager
2.1、功能
NSFileManager使用defaultManager创建单例对象。可以创建文件夹,可以删除、移动、创建文件,判断文件是否存在。
2.2、使用
//缓存文件夹所在路径
NSString *cachesPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
NSLog(@"%@", cachesPath);
//在cachesPath路径下创建一个文件夹
NSString *directoryPath = [cachesPath stringByAppendingPathComponent:@"path"];
NSFileManager *fileManager = [NSFileManager defaultManager]; //创建文件管理类单例对象
//根据路径创建文件夹
NSDictionary *fileDate = @{@"createTime":@"2015-9-9"};
[fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:fileDate error:nil];
//根据路径创建文件(只能写入NSData类型的数据)
[fileManager createFileAtPath:directoryPath contents:data attributes:fileDate];
//删除文件
[fileManager removeItemAtPath:dicPath error:nil]; //删除~/documents/dict.txt
3、NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; //单例
[defaults setValue:@"yfyfyfyfyfyfyfy" forKey:@"username"];
[defaults setValue:@"123" forKey:@"password"];
//注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入到文件中
[defaults synchronize];
//读取
NSString *name = [defaults valueForKey:@"username"];
NSString *pwd = [defaults valueForKey:@"password"];
二、复杂对象持久化(NSKeyedArchiver)
1、复杂对象
复杂对象是在Foundation框架内不存在的数据类,无法通过writeToFile写入到文件内,且至少包含一个实例对象。
由于复杂对象无法通过writeToFile:方法写入文件,只能将复杂对象转化为NSData对象,再进行数据持久化。
2、NSCoding协议
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER
@end
3、复杂对象写入文件
Person.h
//复杂对象归档 一:遵守NSCoding协议
@interface Person : NSObject<NSCoding>
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *gender;
@end
Person.m
#import "Person.h"
@implementation Person //实现NSCoding协议
#pragma mark -- 进行编码 --
- (void)encodeWithCoder:(NSCoder *)coder
{
// [super encodeWithCode:coder]; 如果父类也遵守了NSCoding协议,确保继承的实例变量也能被编码,即也能被归档
[coder encodeObject:self.name forKey:@"name"];
[coder encodeInteger:self.age forKey:@"age"];
[coder encodeObject:self.gender forKey:@"gender"];
}
#pragma mark -- 进行解码 --
- (id)initWithCoder:(NSCoder *)aDecoder
{
// self = [super initWithCoder:aDecoder]; 确保继承的实例变量也能被解码,即也能被恢复
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.gender = [aDecoder decodeObjectForKey:@"gender"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
@end
ViewController.m
类方法进行编码\解码(只能归档一个对象):
NSString *objPath = [cachesPath stringByAppendingPathComponent:@"person.txt"];
[NSKeyedArchiver archiveRootObject:person toFile:objPath];
Person *p2 = [NSKeyedUnarchiver unarchiveObjectWithFile:objPath];
实例方法(可以归档多个对象):
#pragma mark -- 对复杂对象进行持久化(归档\编码) --
//过程:(复杂对象->归档->NSData->writeToFile:)
Person *person = [[Person alloc] init];
person.name = @"yf";
person.age = 20;
person.gender = @"man";
NSMutableData *mtData = [NSMutableData data];
//创建归档器
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:mtData];
//进行归档
[archiver encodeObject:person forKey:@"person"];
//***结束归档
[archiver finishEncoding];
//将归档之后的mtData写入文件
NSString *personPath = [cachesPath stringByAppendingPathComponent:@"person.txt"];
[mtData writeToFile:personPath atomically:YES];
NSLog(@"%@", personPath);
NSLog(@"%@", mtData);
#pragma mark -- 从文件中读取复杂对象(反归档\恢复\解码) --
//过程:(读取文件(NSData)->反归档->复杂对象)
//读取
NSData *readData = [NSData dataWithContentsOfFile:personPath];
//创建反归档工具
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:readData];
//使用反归档工具对readData进行反归档
Person *readPerson = [unArchiver decodeObjectForKey:@"person"];
4、使用NSKeyedArchive进行深复制
比如对一个Person对象进行深复制
// 临时存储person1的数据
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
// 解析data,生成一个新的Person对象
Student *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 分别打印内存地址
NSLog(@"person1:0x%x", person1); // person1:0x7177a60
NSLog(@"person2:0x%x", person2); // person2:0x7177cf0
三、SQLite
1、常用SQL语句
1、说明:创建数据库
CREATE DATABASE database-name
2、说明:删除数据库
drop database dbname
3、说明:备份sql server
--- 创建 备份数据的 device
USE master
EXEC sp_addumpdevice 'disk', 'testBack', 'c:\mssql7backup\MyNwind_1.dat'
--- 开始 备份
BACKUP DATABASE pubs TO testBack
4、说明:创建新表
create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..)
根据已有的表创建新表:
A:create table tab_new like tab_old (使用旧表创建新表)
B:create table tab_new as select col1,col2… from tab_old definition only
5、说明:删除新表
drop table tabname
6、说明:增加一个列
Alter table tabname add column col type
注:列增加后将不能删除。DB2中列加上后数据类型也不能改变,唯一能改变的是增加varchar类型的长度。
7、说明:添加主键: Alter table tabname add primary key(col)
说明:删除主键: Alter table tabname drop primary key(col)
10、说明:几个简单的基本的sql语句
选择:select * from table1 where 范围
插入:insert into table1(field1,field2) values(value1,value2)
删除:delete from table1 where 范围
更新:update table1 set field1=value1 where 范围
查找:select * from table1 where field1 like ’%value1%’ ---like的语法很精妙,查资料!
排序:select * from table1 order by field1,field2 [desc]
总数:select count as totalcount from table1
求和:select sum(field1) as sumvalue from table1
平均:select avg(field1) as avgvalue from table1
最大:select max(field1) as maxvalue from table1
最小:select min(field1) as minvalue from table1
以上内容摘自:
http://www.php100.com/html/webkaifa/database/Mysql/2012/0720/10713.html
2、iOS使用SQLite
iOS操作SQLite需要用到动态库libsqlite3.0.dylib。在Build Phases->Link Binary With Libraries下,点击'+' ,输入sqlite之后添加libsqlite3.0.dylib。
之后,在单例类DatabaseHandle中导入sqlite3.h即可
2.1、连接数据库(若不存在则创建)
1> 获取数据库路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *dbPath = [documentPath stringByAppendingPathComponent:@"student.db"];
2> 连接数据库
int result = sqlite3_open(dbPath.UTF8String, &_db);
_db为实例变量:
{ sqlite3 * _db; }
2.2、关闭数据库连接
int result = sqlite3_close( _db);
2.3、创建表
1> SQL语句-创建表
//create table 表名(字段1 类型, 字段2 类型, ..., 字段n 类型)
NSString *createTable = @"CREATE TABLE students (\
name TEXT,\
number TEXT PRIMARY KEY,\
age INTEGER, stu BLOB)";
2> 执行SQL语句
char *errorMsg = NULL;
int result = sqlite3_exec(_db, createTable.UTF8String, NULL, NULL, &errorMsg);
if (result == SQLITE_OK) {
NSLog(@"创建表成功");
}
2.4、插入、修改、删除数据
0> 打开数据库
1> 新建SQL语句
NSString *insertSQL = @"insert into students(name, age, number, stu) values(?,?,?,?)";
2> 检查语法
sqlite3_stmt *stmt = nil; //跟随指针. 用来让sqlite3_step()执行编译完成的sql语句.在sqlite3中并没有定义该类型指针, 是一个抽象类型(结构体指针)
int result = sqlite3_prepare_v2(_db, insertSQL.UTF8String, -1, &stmt, NULL);
语法正确时:result == SQLITE_OK
3> 字段绑定值
sqlite3_bind_?();
4> 执行编译好的sql语句.
sqlite3_step(stmt);
5> 销毁之前sqlite3_prepare_v2()所检查通过的SQL语句(结束跟随指针)
sqlite3_finalize(stmt);
6> 关闭数据库连接
2.5、查询
0> 打开数据库
1> 新建SQL语句
NSString *selectSQL = @"select * from students";
2> 检查语法
sqlite3_stmt *stmt = nil; //跟随指针
int result = sqlite3_prepare(_db, selectSQL.UTF8String, -1, &stmt, NULL);
3> 取字段下的值
首先,需要用stmt指针遍历:
while (sqlite3_step(stmt)==SQLITE_ROW)
其次,取值:
sqlite3_column_text(sqlite3_stmt *, int iCol);
iCol代表第几列,从0开始
NSString *name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 0)];
当取出的数据为对象时,需要解码
NSData *data = [NSData dataWithBytes:sqlite3_column_blob(stmt, 3) length:sqlite3_column_bytes(stmt, 3)];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Student *stu = [unarchiver decodeObjectForKey:[NSString stringWithFormat:@"student%@",number]];
[unarchiver finishDecoding];
4> 销毁跟随指针
sqlite3_finalize(stmt);
5> 关闭数据库连接
3、Demo
Student.h
@interface Student : NSObject<NSCoding>
@property (nonatomic, retain) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, retain) NSString *number;
#pragma mark -- 自定义初始化方法 --
- (instancetype)initWithName:(NSString *)name number:(NSString *)number age:(int)age;
@end
Student.m
@implementation Student
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.number forKey:@"number"];
[aCoder encodeInt:self.age forKey:@"age"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.number = [aDecoder decodeObjectForKey:@"number"];
self.age = [aDecoder decodeIntForKey:@"age"];
}
return self;
}
- (instancetype)initWithName:(NSString *)name number:(NSString *)number age:(int)age
{
self = [super init];
if (self) {
self.name = name;
self.number = number;
self.age = age;
}
return self;
}
@end
DataBaseHandle.h
@class Student;
@interface DataBaseHandle : NSObject
#pragma mark -- 获取单例对象 --
+ (instancetype)sharedInstance;
- (void)createDB;
#pragma mark -- 插入信息 --
- (void)insertStudent:(Student *)student;
#pragma mark -- 根据学号修改 --
- (void)updateNameOfStudent:(NSString *)name byNumber:(NSString *)number;
#pragma mark -- 根据学号删除 --
- (void)deleteStudentByNumber:(NSString *)number;
#pragma mark -- 查询所有学生信息 --
- (NSArray *)selectAllStudents;
@end
DataBaseHandle.m
#import "DataBaseHandle.h"
#import <sqlite3.h>
#import "Student.h"
@interface DataBaseHandle ()
{
//表示的是数据库连接对象,对数据库中表的操作都基于这个连接.
sqlite3 * _db;
}
@end
@implementation DataBaseHandle
static DataBaseHandle *handle = nil;
+ (instancetype)sharedInstance
{
@synchronized (self) {
if (!handle) {
handle = [[DataBaseHandle alloc] init];
}
return handle;
}
}
#pragma mark -- 连接数据库 --
- (void)createDB
{
//指定存放在沙盒中的数据库路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *dbPath = [documentPath stringByAppendingPathComponent:@"student.db"];
NSLog(@"%@", dbPath);
//打开一个Sqlite数据库(若数据库已存在则创建)并且返回其数据库连接对象
int result = sqlite3_open(dbPath.UTF8String, &_db);
if (result == SQLITE_OK) {
NSLog(@"打开数据库成功!%d", result);
//创建表
[self createTable];
} else {
NSLog(@"打开数据库失败!%d", result);
}
}
#pragma mark -- 创建表 --
- (void)createTable
{
//1.SQL语句-创建表
//create table 表名(字段1 类型, 字段2 类型, ..., 字段n 类型)
NSString *createTable = @"CREATE TABLE students (\
name TEXT,\
number TEXT PRIMARY KEY,\
age INTEGER, stu BLOB)";
//2.执行sql语句
char *errorMsg = NULL;
int result = sqlite3_exec(_db, createTable.UTF8String, NULL, NULL, &errorMsg);
if (result == SQLITE_OK) {
NSLog(@"创建表成功");
} else {
NSLog(@"创建表失败%s", errorMsg);
}
}
#pragma mark -- 关闭数据库连接 --
- (void)closeDB
{
int result = sqlite3_close(_db);
if (result == SQLITE_OK) {
NSLog(@"关闭连接成功");
} else {
NSLog(@"关闭连接失败!%d", result);
}
}
- (void)insertStudent:(Student *)student
{
[self createDB];
//1.SQL语句-插入数据
//insert into 表名(字段1, 字段2, ..., 字段n) values(value1, value2, ..., valuen)
NSString *insertSQL = @"insert into students(name, age, number, stu) values(?,?,?,?)";
sqlite3_stmt *stmt = nil; //跟随指针. 用来让sqlite3_step()执行编译完成的sql语句. 在sqlite3中并没有定义该类型指针, 是一个抽象类型(结构体指针)
//2.检查语法
int result = sqlite3_prepare_v2(_db, insertSQL.UTF8String, -1, &stmt, NULL);
if (result == SQLITE_OK) {
//3.字段绑定值
//第二个参数从1开始,对应insertSQL中第i个?;第三个参数表示绑定值大小,未知写-1
sqlite3_bind_text(stmt, 1, [student.name UTF8String], -1, NULL);
sqlite3_bind_int(stmt, 2, student.age);
sqlite3_bind_text(stmt, 3, student.number.UTF8String, -1, NULL);
//student实例需要编码后存储(转换为NSData)
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:student forKey:[NSString stringWithFormat:@"student%@", student.number]];
[archiver finishEncoding];
sqlite3_bind_blob(stmt, 4, [data bytes], (int)[data length], NULL);
//4.执行编译好的sql语句. 如果执行insert, update, delete, 只需执行一次.
sqlite3_step(stmt);
} else {
NSLog(@"INSERT ERROR %d", result);
}
//5.销毁之前sqlite3_prepare_v2()所检查通过的SQL语句(结束跟随指针)
sqlite3_finalize(stmt);
[self closeDB];
}
#pragma mark -- 修改信息 --
- (void)updateNameOfStudent:(NSString *)name byNumber:(NSString *)number
{
[self createDB];
//1.SQL语句-update
//update 表名 set 字段=? where 字段=?
NSString *updateSQL = @"update students set name= ? where number= ?";
//2.检查SQL语句是否合法
sqlite3_stmt *stmt = nil; //跟随指针
int result = sqlite3_prepare(_db, updateSQL.UTF8String, -1, &stmt, NULL);
if (result == SQLITE_OK) {
//3.绑定
sqlite3_bind_text(stmt, 1, name.UTF8String, -1, NULL);
sqlite3_bind_text(stmt, 2, number.UTF8String, -1, NULL);
//4.执行
sqlite3_step(stmt);
} else {
NSLog(@"UPDATE ERROR!%d", result);
}
//5.销毁
sqlite3_finalize(stmt);
[self closeDB];
}
#pragma mark -- 根据学号删除 --
- (void)deleteStudentByNumber:(NSString *)number
{
[self createDB];
//1.SQL语句-删除
//delete from 表名 where 字段=?
NSString *deleteSQL = @"delete from students where number=?";
//2.检查
sqlite3_stmt *stmt = nil; //跟随指针
int result = sqlite3_prepare(_db, deleteSQL.UTF8String, -1, &stmt, NULL);
if (result == SQLITE_OK) {
//3.绑定
sqlite3_bind_text(stmt, 1, number.UTF8String, -1, NULL);
//4.执行
sqlite3_step(stmt);
} else {
NSLog(@"DELETE ERROR! %d", result);
}
//结束跟随指针
sqlite3_finalize(stmt);
[self closeDB];
}
#pragma mark -- 查询所有学生信息 --
- (NSArray *)selectAllStudents
{
[self createDB];
//1.SQL语句-查询
//select 字段 from 表名 where 字段=?, 字段=?
NSString *selectSQL = @"select * from students";
//2.检查
sqlite3_stmt *stmt = nil; //跟随指针
int result = sqlite3_prepare(_db, selectSQL.UTF8String, -1, &stmt, NULL);
NSMutableArray *stuArray = [NSMutableArray array];
if (result == SQLITE_OK) {
while (sqlite3_step(stmt)==SQLITE_ROW) {
//取字段下的值\
sqlite3_column_text(sqlite3_stmt *, int iCol);\
iCol代表第几列,从0开始
NSString *name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 0)];
NSString *number = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
int age = sqlite3_column_int(stmt, 2);
//反归档(解码)
NSData *data = [NSData dataWithBytes:sqlite3_column_blob(stmt, 3)
length:sqlite3_column_bytes(stmt, 3)];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Student *stu = [unarchiver decodeObjectForKey:[NSString stringWithFormat:@"student%@",number]];
[unarchiver finishDecoding];
NSLog(@"%@ %@ %@ %d", stu.name, stu.number, stu, stu.age);
[stuArray addObject:stu];
}
}
sqlite3_finalize(stmt);
[self closeDB];
return stuArray;
}
@end
使用:
{
...
Student *stu1 = [[Studentalloc] initWithName:@"YF"number:@"0000"age:23];
Student *stu2 = [[Student alloc] initWithName:@"ss" number:@"0001" age:24];
[DBHANDLE insertStudent:stu1];
[DBHANDLE insertStudent:stu2];
stu1.name = @"yyask";
[DBHANDLE updateNameOfStudent:stu1.name byNumber:stu1.number];
// [DBHANDLE deleteStudentByNumber:stu1.number];
[DBHANDLE selectAllStudents];
...
}