iOS开发之数据的持久化存储机制

IOS中数据的持久化保存这块内容,类似于Android中文件的几种常见的存储方式。
对于数据的持久化存储,ios中一般提供了4种不同的机制。
1.属性列表
2.对象归档
3.数据库存储(SQLite3)
4.苹果公司提供的持久性工具Core Data。

其实储存的形式无非就这么几种,而我们还必须要关心的是,这些文件会被放置在那个文件下,然后如何读取。
也就是说:IOS上数据存储,我们要了解的两点,数据存储格式(也就是存储机制),数据存储位置。
1》文件如何存储(如上面4点)
2》文件存储在哪里。
对于数据的操作,其实我们关心的是操作的速率。
就好比在Adnroid中偏好存储,数据库存储,io存储一样。
我大致问了我们公司新来的ios哥们,他说他们培训机构基本对数据操作这块就讲了属性列表和数据库,以及普通的文件存储(比如音视频图这些多媒体数据)。
我就只好先看看书了。

一:应用文件目录

首先我们来看了解下ios数据存储位置,因为只有知道位置路径我们才能去读取数据,而数据的持久化机制不过是针对操作速率来考虑的,
比如我们大致知道属性列表(既键值对形式)的存储熟虑应该高于数据库高于io文件流存储。
我们在选择用何种机制存储数据,主要也是看数据的形式。

一个ios应用安装后大致会有如下文件夹及其对应路径:
iOS开发之数据的持久化存储机制_第1张图片

在mac上看模拟器中应用路径:

/Users/nono/Library/Application Support/iPhone Simulator/5.1/Applications/2D135859-1E80-4754-B36D-34A53C521DE3

你在finder中的home下可能找不到Library这个目录,因为貌似是影藏起来了(我这机器上是,在终端可以看到)。
最后那一窜的类似序列号的东西就是ios自动给应用生成的一组应用唯一识别码最为了应用的home目录名。
其下面就是上图所示了。
书上对这些文件夹介绍:

Document:应用程序将其数据存储在这个文件夹下,基于NSUserDefaults的首选项的设置除外。

简单理解是,基本上我们要操作的一些数据都是存储在这个文件夹下面的

TIPS:这边提下一点,对于ios系统这么分配文件夹,是因为在设备进行同步时,ITunes有选择性的意识来备份文件。

比如我们可以猜到,tmp下的应该就不会备份了。
对于Document文件夹目录路径的获取,API提供了这么一种方法:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  
   NSString *docPath = [paths objectAtIndex:0];  

Library:基于NSUserDefault首选项设置存储在其下Preferences文件夹中,简单来说,这个文件夹一般你很少操作到。

书上对于这部分基本没介绍。估计对于初级部分是跳过了。

Tmp:应用临时存储文件,当不需要时,应用负责删除其下的文件数据。
该文件也提供了目录获取方法:

NSString *tmpDoc = NSTemporaryDirectory();

应用程序文件:这个基本没提到书上,但是我们大致可以猜测,这就是整个应用程序的程序文件夹吧。

好了,以上我们大致解决了我们提到的第一个点,文件存储目录

二:数据存储机制

1.属性列表 Write写入方式

这个其实我们早见过,plist就是,感觉用来存储键值对小数据是最合适,因为速率很高。

这个存储机制很简单,对于前面我们使用过了在plist文件来读取数据填充一些列表,只不过那会plist文件存储位置不同,

用的是Mainbundle什么的来返回文件夹,其实这边我也推测,上面提到有个应用程序文件夹,它下面的文件就是这么来读取的~(反正暂时不管他)

这边不过就是改变了存储位置,数据操作还是一样的

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  
NSString *docPath = [paths objectAtIndex:0];  
NSString *myFile = [docPath stringByAppendingPathComponent:@"my.list"];  
//读取文件  
NSArray *array = [[NSArray alloc] initWithContentsOfFile:myFile];  
//操作完若修改了数据则,写入文件  
[array writeToFile:myFile atomically:YES]; 

2.对象归档
上面的属性列表存储机制,我们都知道,这个机制支持NSArray,NSDictionary,NSData,NSString,NSNumber,NSDate 等等

这些对象直接写入plist文件中。

那么对于一些复杂对象,我要保存整个这个对象数据呢?

反正我是这么觉得,这个机制很像java中的对象整体序列化。当然,这些数据在读取是就需要遵循一种墨守成规的协议了。

首先我们定义的对象类,必须实现NSCoding和NSCopying协议(额,网上说后面这个不实现也可以,我猜是他对象没有copy操作,因此没出错)书本上反正是实现了这两个协议
然后归档中用到的操作类
NSKeyedArchiver
这边我们定义一个对象,h文件中定义两属性,申明要实现的NSCoding和NSCopying协议

Person.h

#import 

@interface Person : NSObject 

@property (nonatomic,retain)NSString *gender; // 性别
@property (nonatomic,retain)NSString *name; // 姓名
@property (nonatomic,assign)int age; // 年龄
@end

Person.m

#import "Person.h"

@implementation Person


// 归档 实际上就是将当前类的属性编码为NSData类型
- (void)encodeWithCoder:(NSCoder *)aCoder{
    
    // 实际编码过程,原理就是将name这个属性的值编码为NSData类型,因为我们解码的时候,需要重新为该类属性赋值,所以需要加标记,也就是Key。
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeObject:self.gender forKey:@"gender"];
    [aCoder encodeInt:self.age forKey:@"age"];
    NSLog(@"执行了归档的方法");
}

// 反归档  因为归档的过程中,我们是将当前类转换为NSData类型,并且储存到了某个文件中,当我们从文件中读取出来数据的时候,基础类型,例如:NSArray等都有initWithContentsOfFile的方法来初始化,但是复杂类型没有类似的方法,只能是反归档来完成此事。
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    
    if (self = [super init]) {
        // 将刚才编码为NSData类型的属性,又通过解码方式变回原来的类型,上面编码过程中,所赋给的key值为何种名称,底下的解码得对应上。
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntForKey:@"age"];
        self.gender = [aDecoder decodeObjectForKey:@"gender"];
         NSLog(@"执行了反归档的方法");
    }
    return self;
}
@end

RootViewController.m

#import "RootViewController.h"
#import "SandBoxPaths.h"
#import "Person.h"
@interface RootViewController ()

@end

@implementation RootViewController


#pragma mark -------- 归档
// 归档并存入沙盒中
- (void)archiverAndSaveSandBox{
    // 归档实际上就是将Person对象转换为NSData类型的数据
    
    
    Person *person = [[Person alloc]init];
    person.name = @"厦航";
    person.age = 24;
    person.gender = @"女";
    
    //  归档的时候,实际是将复杂类对象的属性一一转换为NSData类型,所以是逐步转换的,最终需要将每一步转换好的NSData类型组装为一个完整的NSData,所以我们需要一个可变的NSData来接收它
    NSMutableData *receiveData = [[NSMutableData alloc]init];
    //=========================================================================
    // 归档操作需要借助系统的一个归档工具类来实现,这个类实际的操作就是将Person对象转换为NSData类型的数据,并赋值给刚才咱们初始化的NSData对象
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:receiveData];
    //=========================================================================
    // 归档开始
    [archiver encodeObject:person forKey:@"person"];
    // 需要有一个标志,让我们知道归档完成了,我们的receiveData中有值了 (不然会出错误)
    [archiver finishEncoding];
    
    // 已经转换完成的,就可以进行数据持久化了
    NSString *pathString = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"person.DA"];
    
    [receiveData writeToFile:pathString atomically:YES];
    NSLog(@"-------%@",pathString);
    
}
#pragma mark ----- 反归档
- (void)unArichiver{
    // 反归档,实际上就是将NSData类型转换为复杂类型对象,就是本例中的person对象
    
    NSString *pathString = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"person.DA"];
    NSData *data = [[NSData alloc]initWithContentsOfFile:pathString];
    // 反归档,反归档也需要借助系统的一个反归档工具类来实现
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];
    // 开始反归档
   Person *person =  [unarchiver decodeObjectForKey:@"person"];
    
    NSLog(@"name-------%@",person.name);
    
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self archiverAndSaveSandBox];
    [self unArichiver];
    
    }
    

三.数据库存储

1.这里必须先要导入一个系统文件。在xcode7.0之后把后缀改了,现在叫libsqlite3.tbd

//里面有操作sqlite数据库的所有函数,我们要操作数据库,就需要导入libSqlite3的系统库 路径:TARGETS -> Build Phases -> Link Binary with Libraries

导入头文件

#import   //里面有操作sqlite数据库的所有函数,我们要操作数据库,就需要导入libSqlite3的系统库 路径:TARGETS -> Build Phases -> Link Binary with Libraries
#import "SandBoxPaths.h"
@implementation RootViewController
#pragma mark ------ 打开或者创建数据库
// 打开或者创建数据库
- (sqlite3 *)openOrCreateDB{
    // 首先我们需要一个保存数据库的文件路径
    NSString *dbPath = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"db.sqlite" ];
    // 数据库的句柄
    sqlite3 *sqlite3 = NULL;
    // 如果数据库已经存在,此函数就是打开当前数据库文件,如果该数据库文件不存在,那么此函数就是创建数据库文件并打开。
    // filename:数据库文件的路径
    // ppDb: 数据库句柄此变量的指针的指针。当前数据库创建或者打开成功之后,会将地址指针保存在该参数中,这样,此句柄变量就可以通过指针来操作数据库。
    // 由于此函数为C函数,但是dbPath为OC对象,所以需要将OC字符串转换为C字符串
    // 此函数有返回值,sqlite3中对所有的操作,只要没有返回数据结果集的,都会有一个int的返回值,来标识此操作是否进行成功。
   int result =  sqlite3_open(dbPath.UTF8String, &sqlite3);
    
    if (result == SQLITE_OK) {
        
        NSLog(@"数据库打开成功");
        
        return sqlite3;
    }else{
        NSLog(@"数据库打开失败");
        return NULL;
    }
    
}
#pragma mark ---------- 执行无返回结果集的SQL操作
// 执行无返回结果集的SQL操作
- (BOOL)exeSqlWithSQLString:(NSString *)sqlStr{
    // 打开数据库
    sqlite3 *sqlDB = [self openOrCreateDB];
    // 执行SQL的函数
    // 第一个参数:数据库的句柄,可以理解为就是数据库
    // 第二个参数:所要执行的sql语句
    // 第三个参数:执行完SQL之后的回调方法。
    // 第四个参数:回调方法的第一个参数
    // 第五个参数:错误日志,等同于OC中的NSError,这里是char类型。
   int result =  sqlite3_exec(sqlDB, sqlStr.UTF8String, NULL, NULL, NULL);
    if (result == SQLITE_OK) {
        NSLog(@"语句执行成功");
        // 关闭数据库
        sqlite3_close(sqlDB);
        return YES;
    }else{
        NSLog(@"语句执行失败");
        // 关闭数据库
        sqlite3_close(sqlDB);
        return NO;
    }
}
#pragma mark ----- 查询数据库
// 查询数据库
- (NSArray *)queryDBWithSqlString:(NSString *)sqlStr{
    
    
    // 初始化一个可变数组,一会用来盛放所有的结果
    NSMutableArray *resultMutableArray = [[NSMutableArray alloc]init];
    // 打开数据库
    sqlite3 *sqlDB = [self openOrCreateDB];
    // 用来保存记录的指针对象
    sqlite3_stmt *stament = NULL;
    // 用来检查SQL的函数,如果SQL语句编译无问题,就将编译好的SQL保存到stament中
   int result =  sqlite3_prepare(sqlDB, sqlStr.UTF8String, -1, &stament, NULL);
    
    if (result == SQLITE_OK) {
        while (sqlite3_step(stament) == SQLITE_ROW){
            
            // while每执行一次,就会有一条新记录,所以我们每次都需要一个新的字典,也就是初始化好的字典来盛放记录
            NSMutableDictionary *rowDic = [NSMutableDictionary dictionary];
         // 每执行一次step函数,都会在stament中保存一条完整的记录。
            // 取出一条记录中的某个字段
            // 第二个参数是指取出第几列的字段值
            
            int number = sqlite3_column_int(stament, 0);
            [rowDic setObject:[NSNumber numberWithInt:number]forKey:@"number"];
            
        const unsigned char *name = sqlite3_column_text(stament, 1);
            NSString *nameString = [NSString stringWithCString:(const char *)name encoding:NSUTF8StringEncoding];
//            NSString *nameString = [NSString stringWithUTF8String:(const char *)name];
            [rowDic setObject:nameString forKey:@"name"];
            const unsigned char *gender = sqlite3_column_text(stament, 2);
            NSString *genderString = [NSString stringWithCString:(const char *)gender encoding:NSUTF8StringEncoding];
            [rowDic setObject:genderString forKey:@"gender"];
            
            
            //组装好字典之后,将该字典放入数组
            [resultMutableArray addObject:rowDic];
        }
    }
    
    // 关闭数据库
    sqlite3_close(sqlDB);
    // 释放stament所持有的资源
    sqlite3_finalize(stament);
    return resultMutableArray;
}

这里封装了DataBaseHelper单例可以直接拿来使用。

四。Core Data存储机制

大致浏览下基本感觉就是将对象归档搞成了可视化和简单化。

这块内容比较多。网上资料也挺丰富的。

暂时不做介绍了。

总结下:其实对于ios数据存储,最常用和主要要掌握的就是属性列表和数据库,因为两个是出镜率比较高的。

其他可能在数据存明显体现出储优势时,我们会去考虑用另外两种机制。

基础的来说,必须掌握属性列表和sqlite的操作存储。

你可能感兴趣的:(iOS开发之数据的持久化存储机制)