iOS数据持久化以及本地数据存取的相关操作

2016-11-29  本文已影响0人  丶生如夏花

一、应用结构

  1. 每个应用都有自己的独立沙盒, 即文件系统目录, 除自身外不可对其进行访问
  2. 应用沙盒结构分析:

二、生成保存路径

根据保存数据类型不同, 采取不同的保存路径,以下示例均统一放在Documents文件夹下

1. 沙盒根目录拼接Documents文件夹路径生成保存路径(不推荐)

NSString *homeDirectory = NSHomeDirectory();
NSString *documentPath = [homeDirectory stringByAppendingPathComponent:@"Documents"];

2. 通过用户文件夹下查找, 即NSUserDomainMask参数的含义(推荐)

//  由于程序有且只有独立拥有一个Documents文件夹, 所以数组中仅仅只有一个元素, 无论是第一个对象还是最后一个对象均可
NSArray *applicationFilesArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//  对要存储的的内容添加文件名, 第一种方法会自动添加'/', 故可以直接添加文件名, 而第二种需要手动添加
NSString *documentPath = [applicationFilesArray firstObject];
    
NSString *filePath1 = [documentPath stringByAppendingPathComponent:@"newInfo.plist"];
NSString *filePath2 = [documentPath stringByAppendingString:@"/newInfo.plist"];

//  可以发现这两个路径相同
NSLog(@"filePath1 Address:%@", filePath1);
NSLog(@"filePath2 Address:%@", filePath2);

3. 其他路径

[NSTemporaryDirectory() stringByAppendingPathComponent:@"newInfo.plist"]

三、应用数据存储的常用方式主要以五种形式为主:

XML属性列表(plist)、偏好设置(Preference)、NSKeyedArchiver(NSCoding)、SQLite3Core Data

1、 属性列表plist:

对象支持的主要类型包含NSStringNSDictionaryNSArrayNSDataNSNumber等, 其他本数据类型可以通过NSData进行转换存储

//  创建文件方法如下:
//  创建可变字典, 初始化设置值, 如果后期需要修改值, 只需要重新设置, 再保存一次即可
//  如果采用不可变字典, 首次写入依然可以生成, 但是后期不可更改信息
NSArray *applicationFilesArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [applicationFilesArray firstObject];
NSString *filePath = [documentPath stringByAppendingString:@"/newInfo.plist"];

NSMutableDictionary *saveInfoDic = [NSMutableDictionary dictionary];
[saveInfoDic setObject:@"Leo" forKey:@"name"];
[saveInfoDic setObject:@"Male" forKey:@"sex"];
[saveInfoDic setObject:@"25" forKey:@"age"];
[saveInfoDic writeToFile:filePath atomically:YES];
    
[saveInfoDic setObject:@"Female" forKey:@"sex"];
[saveInfoDic writeToFile:filePath atomically:YES];

//  读取文件方法如下:
//  从目标路径下获取字典, 获取单个属性键入键值即可
NSDictionary *readInfoDic = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(@"age:%@", readInfoDic[@"age"]);
NSLog(@"name:%@", readInfoDic[@"name"]);

2、 偏好设置Preference:

对象支持类型包含NSStringNSDictionaryNSArrayNSDataNSNumber等, 其他基本数据类型可以通过NSData进行转换存储

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *testDic = [NSMutableDictionary dictionary];
    
//  可以对键值对进行初始化
[defaults setObject:@"Leo" forKey:@"userName"];
[defaults setFloat:18.0f forKey:@"age"];
[defaults setBool:YES forKey:@"auto_login"];
    
//  该操作是对数据进行一个登记注册
[defaults registerDefaults:testDic];
    
//  取得之前设置的偏好设置
NSUserDefaults *getDefaults = [NSUserDefaults standardUserDefaults];
    
//  对应上式初始化, 再取得偏好设置文件后, 已经在册的键将被更新, 不存在的键将被创建
[getDefaults setFloat:25.0f forKey:@"age"];
[getDefaults setObject:@"Male" forKey:@"sex"];
    
//  将数据保存到本地磁盘当中进行保存, 此时为立即执行保存操作, 如果不执行该方法, 也许会造成数据还未保存完毕的结果
[getDefaults synchronize];

//  读取偏好设置的键值
NSLog(@"age:%@", [defaults objectForKey:@"age"]);
NSLog(@"sex:%@", [defaults objectForKey:@"sex"]);

3、 NSKeyedArchiver(NSCoding):

支持类型同plist, 如果自定义类对象要采取该方法, 则须遵守NSCoding协议

1. 归档一个已存在的对象到Documents下

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
//  后缀名为archive (默认archive文件是加密的, 而设置为plist后缀的话可以进行明文阅读)
NSString *filePath = [documentPath stringByAppendingPathComponent:@"archiverExp.archive"];

//  1、归档一个已有对象如数组对象到Documents下
NSArray *keyArchiveArray = @[@"testA", @"testB"];
[NSKeyedArchiver archiveRootObject:keyArchiveArray toFile:filePath];
    
//  解码恢复对象
NSArray *unarchiveArray = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
//  打印解码信息
NSLog(@"unarchiveArray:%@", unarchiveArray);

2. 归档自定义对象

首先创建自定义对象, 以下几种归档均采用该对象为示例

#import <Foundation/Foundation.h>

@interface Example : NSObject<NSCoding>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger number;

@end
#import "Example.h"

@implementation Example

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    //  如果该类为子类, 父类遵守协议, 则需self = [super initWithCoder:aDecoder];
    self.name = [aDecoder decodeObjectForKey:@"name"];
    self.number = [aDecoder decodeIntegerForKey:@"number"];
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    //  如果该类为子类, 父类遵守协议, 则需[super encodeWithCoder:aCoder];
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.number forKey:@"number"];
}

@end

进行归档操作

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
//  后缀名为archive (默认archive文件是加密的, 而设置为plist后缀的话可以进行明文阅读)
NSString *filePath = [documentPath stringByAppendingPathComponent:@"archiverExp.archive"];

//  2、归档自定义对象
Example *archiveExample = [[Example alloc] init];
archiveExample.name = @"eg";
archiveExample.number = 1;
[NSKeyedArchiver archiveRootObject:archiveExample toFile:filePath];
    
//  对象类型匹配, 解码恢复对象
Example *unarchiveExample = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"unarchiveExample name:%@", unarchiveExample.name);
NSLog(@"unarchiveExample number:%ld", unarchiveExample.number);

3. 归档多个自定义对象

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
//  后缀名为archive (默认archive文件是加密的, 而设置为plist后缀的话可以进行明文阅读)
NSString *filePath = [documentPath stringByAppendingPathComponent:@"archiverExp.archive"];

Example *archiveExample = [[Example alloc] init];
archiveExample.name = @"eg";
archiveExample.number = 1;
Example *archiveExample2 = [[Example alloc] init];
archiveExample2.name = @"eg2";
archiveExample2.number = 2;

//  3、归档多个自定义对象
//  建立可变数据区, 并连接到一个NSKeyedArchiver对象
NSMutableData *archiveData = [NSMutableData data];
NSKeyedArchiver *archive = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData];

//  开始对对象存档, 存档的数据都会储存在NSMutableData中
[archive encodeObject:archiveExample forKey:@"eg1"];
[archive encodeObject:archiveExample2 forKey:@"eg2"];
    
//  指定完对象存档, 调用该方法告诉系统存档完毕
[archive finishEncoding];
    
//  将存档好的数据写入文件
[archiveData writeToFile:filePath atomically:YES];
    
//  解码恢复对象
NSData  *unarchiveData = [NSData dataWithContentsOfFile:filePath];
    
//  根据数据, 解析出对应的NSKeyedUnarchive对象
NSKeyedUnarchiver *unarchive = [[NSKeyedUnarchiver alloc] initForReadingWithData:unarchiveData];
Example *unarchiveExample = [unarchive decodeObjectForKey:@"eg1"];
Example *unarchiveExample2 = [unarchive decodeObjectForKey:@"eg2"];
    
//  解码恢复完毕, 调用该方法告诉系统解码完毕, 此时unarchive不能再解码对象
[unarchive finishDecoding];

//  打印解码信息
NSLog(@"unarchiveExample name:%@", unarchiveExample.name);
NSLog(@"unarchiveExample2 name:%@", unarchiveExample2.name);

//  同理也可以将多个对象放到数组中进行归/解档, 在归/解档操作时会默认对数组中的对象自动进行encodeWithCoder:和initWithCoder的方法

4. 利用归档来实现深复制

Example *archiveExample = [[Example alloc] init];
archiveExample.name = @"eg";
archiveExample.number = 1;

//  4、利用归档来实现深复制
//  临时存储数据
NSData *tempData = [NSKeyedArchiver archivedDataWithRootObject:archiveExample];
    
//  解析data, 生成对象
Example *waitForCopyExample = [NSKeyedUnarchiver unarchiveObjectWithData:tempData];
    
//  通过内存地址的打印, 可以发现已经完成了深拷贝操作
NSLog(@"example1 address:0x%x", archiveExample);
NSLog(@"example2 address:0x%x", waitForCopyExample);

四、SQLite3

最常用的开源数据库, 内存开销小, 操作都是基于C底层, 效果好, 除开SQL语句需要多使用熟练以外

  1. 常用的5种数据类型: textintegerfloatbooleanblob
  2. 在iOS开发中要进行其使用, 需要添加库文件libsqlite3.0.tbdlibsqlite3.tbd均可, 后者是一个原始库, 前者是一个指针指向最新的数据库, 如果更新则无需手动修改
  3. 添加完库文件后, 在使用处还需导入<sqlite3.h>, 如果多处地方需使用, 不妨写入预编译头文件进行导入
  4. 不排除现在部分开发者使用fmdb等第三方数据库框架可以省去写SQL语句, 但是把底层数据库知识储备完善, 有百利而无一害
  5. 更多更清楚的了解SQL

1. 创建数据库文件, 并打开

NSString *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [documentDirectory stringByAppendingPathComponent:@"dataBase.sqlite"];
    
sqlite3 *sqliteDB = nil;
//  sqlite3_open()语句: 其将在指定路径下打开数据库, 如果不存在, 则新建数据库
//  通过openResult的返回结果可知道操作是否成功
int openResult = sqlite3_open([filePath UTF8String], &sqliteDB);
if (openResult != SQLITE_OK) {
    NSLog(@"打开数据库文件失败");
    return;
}

2. 创建表格

NSString *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [documentDirectory stringByAppendingPathComponent:@"dataBase.sqlite"];
    
sqlite3 *sqliteDB = nil;
//  sqlite3_open()语句: 其将在指定路径下打开数据库, 如果不存在, 则新建数据库
//  通过openResult的返回结果可知道操作是否成功
int openResult = sqlite3_open([filePath UTF8String], &sqliteDB);
if (openResult != SQLITE_OK) {
    NSLog(@"打开数据库文件失败");
    return;
}

//  创建一个错误信息
char *errorMsg = nil;
    
//  如果在创表语句中去掉if not exists, 将会出现“工作表已存在”的报错语句
//  sqlite3_exec()可以执行任何SQL语句, 对于表创建以及表中数据的增删改都可以进行操作
char *createTableSQL = "create table if not exists t_person(id integer primary key autoincrement, name text, age integer);";
int executeResult = sqlite3_exec(sqliteDB, createTableSQL, NULL, NULL, &errorMsg);
if (executeResult != SQLITE_OK) {
    NSLog(@"数据库创建表格失败, 原因为:%s", errorMsg);
    return;
}

//  执行完操作, 关闭数据库
sqlite3_close(sqliteDB);

3. 数据库的值绑定

NSString *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [documentDirectory stringByAppendingPathComponent:@"dataBase.sqlite"];
    
sqlite3 *sqliteDB = nil;
//  sqlite3_open()语句: 其将在指定路径下打开数据库, 如果不存在, 则新建数据库
//  通过openResult的返回结果可知道操作是否成功
int openResult = sqlite3_open([filePath UTF8String], &sqliteDB);
if (openResult != SQLITE_OK) {
    NSLog(@"打开数据库文件失败");
    return;
}

//  根据需求编写SQL插入语句
char *insertSQL = "insert into t_person(name, age) values (?, ?);";
sqlite3_stmt *insertStmt = nil;
if (sqlite3_prepare_v2(sqliteDB, insertSQL, -1, &insertStmt, NULL) == SQLITE_OK) {
    sqlite3_bind_text(insertStmt, 1, "Leo", -1, NULL);
    sqlite3_bind_int(insertStmt, 2, 27);
}
if (sqlite3_step(insertStmt) != SQLITE_DONE) {
    NSLog(@"数据插入错误");
}
    
//  关闭数据库句柄
sqlite3_finalize(insertStmt);
//  关闭数据库
sqlite3_close(sqliteDB);

4. 数据库的数据查询

NSString *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [documentDirectory stringByAppendingPathComponent:@"dataBase.sqlite"];
    
sqlite3 *sqliteDB = nil;
//  sqlite3_open()语句: 其将在指定路径下打开数据库, 如果不存在, 则新建数据库
//  通过openResult的返回结果可知道操作是否成功
int openResult = sqlite3_open([filePath UTF8String], &sqliteDB);
if (openResult != SQLITE_OK) {
    NSLog(@"打开数据库文件失败");
    return;
}

char *querySQL = "select id, name, age from t_person;";
sqlite3_stmt *queryStmt = nil;
if (sqlite3_prepare_v2(sqliteDB, querySQL, -1, &queryStmt, NULL) == SQLITE_OK) {
    while (sqlite3_step(queryStmt) == SQLITE_ROW) {
        int _id = sqlite3_column_int(queryStmt, 0);
        char *_name = (char *)sqlite3_column_text(queryStmt, 1);
        int _age = sqlite3_column_int(queryStmt, 2);
        NSString * name = [NSString stringWithUTF8String:_name];
        NSLog(@"id:%i, name:%@, age:%i", _id, name, _age);
    }
}
sqlite3_finalize(queryStmt);
sqlite3_close(sqliteDB);

只想日后回头看看来时的路,能发现自己并不是一无所有。

上一篇 下一篇

猜你喜欢

热点阅读