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

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

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

一:应用文件目录

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

一个ios应用安装后大致会有如下文件夹及其对应路径:

在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 <Foundation/Foundation.h>

@interface Person : NSObject <NSCoding>

@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 <sqlite3.h>  //里面有操作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的操作存储。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,852评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,587评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,401评论 25 707
  • 武汉马拉松已经过去一个多礼拜,对于很多人来说42.195公里可能是一个遥不可及的概念名词。但是人生不曾与跑步相识或...
    嘴角的记忆阅读 537评论 0 51
  • «军谶»曰:柔能制刚,弱能制强。柔者,德也;刚者,贼也。弱者人之所助,强者怨之所攻。柔有所设,刚有所施;弱有所用,...
    luozi阅读 1,442评论 0 0