iOS狂暴之路—iOS中应用的数据存储方式解析

iOS技术篇 尼古拉斯.赵四 3930℃

一、前言

前面一篇文章中已经介绍了 iOS应用中的视图控制器知识点,而本文不会按照常理来介绍View的知识点,而是先介绍iOS中的数据存储知识点,因为关于View的知识点太多了,后面会连续详细介绍一下。这篇先来看一下iOS中的数据存储功能分析。每一个iOS应用和Android应用一样,都有其对应的沙盒存储自己的数据,但是iOS和Android有一个区别就在于没有SD卡的概念了,也就说在iOS中应用的数据只能保存到自己的沙盒中。这也可以看出来iOS为了应用的安全考虑。

在开发Android的时候都知道主要的存储方式有:数据库、SD卡、SharedPreferences.xml、应用沙盒的其他目录、数据序列化到本地文件、网络等。而在iOS中其实除了SD卡这种方式之外,其他都是与之类似的,所以本文就会一一介绍着四种存储方式:数据库、首选项、归档解档、plist文件。关于网络后续会单独详细介绍。这里就不在介绍了。下面就来开始一一介绍这四种存储方式。

 

二、沙盒机制和沙盒目录结构

在讲解这四种方式之前,我们得先来做一件事,就是了解一下iOS中的应用沙盒目录结构,Android中其实也是类似,沙盒数据都放在/data/data/xxxx/下面,数据库是在databases目录下,SharedPreferences.xml存放在shared_prefs目录下,还有files目录,cache目录等。在iOS中其实结构也是非常类似的:

iOS应用都被限制在“沙盒”中,“沙盒”相当于一个加了仅主人可见权限的文件夹,苹果对沙盒有以下几条限制。
1> 应用程序可以在自己的沙盒里运作,但是不能访问任何其他应用程序的沙盒。
2> 应用程序间不能共享数据,沙盒里的文件不能被复制到其他应用程序文件夹中,也不能把其他应用程序文件夹中的文件复制到沙盒里。
3> 苹果禁止任何读、写沙盒以外的文件,禁止应用程序将内容写到沙盒以外的文件夹中。
4> 沙盒根目录里有三个文件夹:Documents,一般应该把应用程序的数据文件存到这个文件夹里,用于存储功能。
沙盒机制是一种安全体系,它规定了应用程序只能在本应用程序沙盒中读取文件,不可以访问其他地方的内容。所有的非代码文件都保存在这个地方,比如图片、音频、视频、属性列表(偏好设置)和文本文件等。

优点:是安全每个应用程序都在自己的沙盒内不能随意跨越自己的沙盒区访问别的应用程序沙盒的内容,应用程序向外请求或接受数据都需要经过权限认证
缺点:文件访问受限访问文件不灵活

iOS中获取沙盒路径使用NSHomeDirectory()方法即可,我们可以打印一下看看效果,一般使用模拟器的时候目录都是非常长的,我们可以使用Finder中command+shift+g前往文件夹把输出它打印的沙盒路径:

我们可以复制这个路径查看这个目录:

如上图我们看到的NSHomeDirectory()获取的是程序的主目录,有三个子目录:Documents、Library、tmp

1、Library下有Caches和Preferences目录

1》Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时 不会备份该目录 。一般存储体积大、不需要备份的非重要数据。
2》Preferences:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时 会备份该目录。

2、Documents:保存应用运行时生成的需要持久化的数据,iTunes同步设备时 会备份该目录 。例如,游戏应用可将游戏存档保存在该目录。
3、tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。

那么我们一般获取这些目录有很多方式:

第一种方式直接通过目录名构造:比如这里获取Documents目录:
[NSHomeDirectory() stringByAppendingPathComponent:@”Documents”];

其他的都与之类似了。这种方式有一个不好就是文件目录都是手动写死的,哪天如果苹果说不叫这个文件目录名字了,那就有很大问题了。
第二种方法完全采用系统提供的方法获取:比如这里获取Documents目录:
[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES)[0]; 

如果后续要在这个目录中新建文件,直接可以使用NSString的stringByAppendingPathComponent:方法获取文件路径名即可。

其实到这里已经简单介绍了iOS中程序的沙盒数据目录结构,下面就要开始介绍那四种存储方式了,但是在介绍之前为了后续工作的方便,这里我们给NSString类定义一个分类,增加一个可以获取Documents目录下的文件名路径方法,这样我们后面的例子只要导入这个分类,然后直接使用NSString的这个方法即可获取到Documents目录下的文件名了,不然每次都要构造一个Documents路径有点费劲了,同时这里也可以看到分类的作用,分类定义很简单,比如这里是给NSString添加一个分类,那么分类名就是NSString+XXX.h即可。其中XXX是可以随便起的名称。

后续的话,我们如果想在Documents目录下新建一个demo.data文件,直接可以这么干:[@”demo.data” documentAppend]方法调用即可。后续可以看到具体的用法。

 

三、存储方式解析

第一种:归档解档

这个其实非常简单,就是Java中的序列化概念,可以把一个对象变成一个字节流数据,在Java中可以通过ObjectInputStream和ObjectOutputStream流进行操作,但是有一个要求就是操作的对象必须要实现序列化接口:Serializable。在这里的归档和解档操作原理类似,如果要把一个对象变成NSData对象(NSData对象在iOS中非常常见,就有点类似于Android中的ByteBuffer类,就是二进制数据流,操作也是非常方便的,比如读写文件,网络数据都要用到它),然后在写到文件中即可。当然这里和Java类似,操作的类必须实现一个协议:NSCoding,也就说只有实现了这个协议的对象才可以进行归档和解档操作。下面不多说了直接看代码吧:

这里为了演示方便,新建一个Student类,实现NSCoding协议,然后进行归档操作,在进行解档操作:

为了方便,这里定义一个属性。然后定义好这个对象之后,就需要实现协议中的方法了:

这里需要实现两个方法,一个是把对象编码的方法,一个是对象的解码方法,从这里可以看到这个更像Android中的Parcel类了,其实在Android中我们一般用的最多的就是Parcel类了,主要用于进程间通信数据传输过程中,但是一般不会把这个类写到文件中,不过也是可以实现的,可以通过marshall和unmarshall这两个方法读写到文件中。不过貌似一般不这门干。一般是采用Serializable进行序列化,所以我在开始的时候没有提到Parcel类。其实这个NSCoding协议和Parcelable接口更相似。写过Android中的Parcel类这些都不难。定义好了这个类之后,下面咋们就可以开始进行归档和解档操作了:

看到这里的代码非常简单,同时用到了上面的那个分类功能,这里可以看到代码就精简了很多。归档使用NSKeyedArchiver:toFile:方法即可。解档使用NSKeyedUnarchiver:方法即可。我们可以去查看这个文件信息:

只是一个二进制数据文件了。

第二种:应用首选项

这个和Android中的SharedPreferences.xml非常类似,但是在iOS中你会发现这种存放方式比Android要简单很多。在Android中还记得操作SP.xml的时候,读写操作也要几行代码。还有apply和commit的区别啥的。还要考虑性能问题。在iOS中保存一个值是一行代码,读取一个值,也是一行代码。下面来简单看一下代码案例:

主要使用了NSUserDefaults类进行操作,可以通过standardUserDefaults静态方法获取实例,然后如果要保存值就调用具体类型的方法,而在这里NSUserDefaults可以保存的类型可以是这些类型:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary、基本数据类型

其中NSData是对象序列化之后的二进制数据上面已经介绍过了,所以从这里可以看到我们还可以使用NSUserDefaults来进行对象的归档和解档操作的。然后还可以存储数据集合类型的,但是这里一定要注意的是保存和读取的集合类型都是不可变的,这一点很重要,如果读写了可变的集合类型是会报错的。最后说明的一点也是很简单的,就是这里存储的数据的key是唯一的,如果用到了相同的key值,那么后面的值会覆盖前面的值。

注意:

上面说到的不可变的逻辑是保存到文件中的时候操作的集合是不可变的,但是我们可以这么操作:

其实这么操作是不会报错的,但是这里还是需要注意的是保存到文件中以及从文件中读出来的对象都是不可变的。我们这里打印了读取出来的对象类型:

是__NSCFArray类型的,这个类型其实是NSArray在运行时的实际操作对象,他是C语言实现的,是NSArray的类簇。或者我们这么做来看看效果,就是给数据添加元素看怎么样:

运行的时候会发现崩溃了:

也证实了返回来的是一个不可变类型了。

这里我们保存了一个NSInteger值到key=age中,而这个NSUserDefaults保存的数据的文件,在上面分析了沙盒数据目录结构中已经介绍了,是保存在Library/Preferences/目录下,文件名是:应用的包名.plist

这个保存的格式是一个plist文件,这个文件类似于Java中的Property文件,我们可以直接使用Xcode打开查看信息:

其实内部可以看到是一个Dictionary结构来进行保存数据的。

第三种:plist文件存储

这个方式其实也不算是单独的一种存储方式了,但是因为他比较特殊就顺便拿出来介绍一下了,这个plist文件格式存储在iOS中是非常常见的一种文件格式存储方式了,比如上面的NSUserDefaults方式最终保存的文件格式就是plist的,这里我们会介绍iOS中的那些集合类型数据可以直接保存到这个文件中。

看到这个操作是不是非常简单,每个集合类型都有对应的一个读写文件的方法,我们这里把数据保存到了Documents/demo.plist中,我们可以去查看一下效果:

看到这里存储的时候就是一个NSArray格式的。看起来更简洁了方便了。

第四种:数据库存储方式

关于数据库这种存储方式是非常实用的,Android和iOS都是采用同一个数据库sqlite,只是Android在使用的时候使用了Java语言,其实核心部分操作都是在native层,所以对于iOS来说是直接可以通过C代码来操作这个数据库即可。同时可以看到从效率和使用上来说都会比Android好一点。

从对以往的大型数据库还是Android中使用的sqlite数据库来说,大部分都分为这么几步来操作:打开数据库,建立连接,执行对应的数据库语句,最终得到我们想要的数据和结果。最后记得关闭数据库。一般操作数据库也就是增删改查这四个操作。在Android中我们需要通过打开数据库获取数据库对象,然后用数据库对象执行sql语句即可。在iOS中也是类似的操作,为了看到效果,这里演示:创建表格,插入数据,查询数据。对于删除和更新操作大同小异就不单独介绍了。

1、打开数据库

打开数据库的时候需要传入一个数据库文件名路径,然后调用sqlite3_open函数打开数据库即可:

这里的逻辑和Android中类似,如果第一次发现数据文件不存在就创建,如果存在就不创建了,看到这个函数有两个参数:一个是数据库文件名路径,一个是打开之后的数据库指针。同时还会返回一个状态码。

注意:这里我们会发现在C语言中指针的重要性了,看到这里用了指针可以做到一个函数返回多个值的效果。所以说指针就是C语言的灵魂。非常强大!

关于这个返回值的状态码,我们也是需要判断是不是成功的,也就是数据库是否成功打开了,使用SQLITE_OK常量做判断。

2、创建表格

上面成功打开数据库之后,就可以使用数据库指针来执行对应的sql语句了,当然这里第一个语句肯定是创建一个表格了,关于sql语法不多介绍了。这里使用了sqlite3_exec来执行sql语句的,需要传入数据库指针,同时为了能够返回多个信息,比如这里还想获取执行的错误信息,再次使用到了指针,定义一个错误信息字符串指针传入。同时返回一个sql语句的执行状态,这里还需要做一次成功判断。

注意这里的创建表格的语法做了一次判断表格是否已经存在了逻辑。

3、插入数据

上面已经创建完了person数据表,下面就来继续看看如何插入一条数据,插入数据依然是使用sql语句,这里和Android中采用的方式是一样的,利用占位符来进行字段值的填充:

首先调用sqlite3_prepare_v2函数,来检查一下语法有没有错误,并且得到一个sqlite3_stmt类型指针,用于后续的占位符补位值。如果发现语法没有问题,继续调用sqlite_bind_textsqlite_bind_int函数对占位符进行填充值,这里每种数据类型都有对应的函数。填充完毕之后,直接调用sqlite2_step函数执行语句,这里依然有一个返回值判断操作是否成功完成。

4、查询数据

上面已经成功的插入一条数据了,下面继续来把这条数据查询出来吧,操作也是很简单的,创建一个查询语句,这里没有条件判断了,如果有的话也是有相对应的条件占位符的,当然占位符填充操作和上面类似:

创建完查询语句之后,依然使用sqlite3_prepare_v2来进行语法检查,如果成功了,就开始使用sqlite3_step函数来进行每行内容的遍历,然后在循环内部开始读取每行内容信息,使用sqlite3_column_textsqlite3_column_int函数获取对应字段的值,这里使用的是字段的索引而不是字段的名称来获取的。索引下标从0开始

5、关闭数据库

在每次操作完数据库之后一定要记得关闭数据库,关闭数据库非常简单,调用sqlite3_close函数即可。

到这里我们就介绍完了数据库的存储方式了,可以看到是纯种的C语言调用,效率非常高的。当然我们也看到上面操作数据库的步骤还是有点繁琐的,所以后面文章会详细介绍一个数据库操作框架,到时候会用框架来代替这种纯种方式操作。

注意:说到iOS中的数据库操作其实还有一个非常重要的知识点就是CoreData,这个是对数据库和对象之间进行的封装操作,类似于Hibernate的ORM方式,把一个类和数据库中的表格对应起来,操作对象的时候就可以操作数据库中的对应表格数据了。因为非常的庞大,而且多方打听了iOS老司机的心得是觉得这个功能太庞大了,在项目中一般不怎么使用,所以本文也就不做太多的介绍了,如果以后真的要用到的话,在去查看应该问题不大。

 

四、知识回顾

上面已经就介绍完了iOS中的主要四种数据存储方式,在开始也介绍了iOS中的沙盒机制以及沙盒目录结构知识,下面在来详细总结一下作为一个回顾:

第一、应用的沙盒目录结构知识

通过NSHomeDirectory()获取的是程序的主目录,有三个子目录:Documents、Library、tmp.

1、Library下有Caches和Preferences目录,其中:

1>Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时 不会备份该目录 。一般存储体积大、不需要备份的非重要数据。

2>Preferences:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录,上面的NSUserDefaults存放的值得plist文件就存放在这里的。

2、Documents:保存应用运行时生成的需要持久化的数据,iTunes同步设备时 会备份该目录 。例如,游戏应用可将游戏存档保存在该目录,在这个目录我们看到上面的数据库文件,plist文件,归档解档文件都是存放在这个目录中的。

3、tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。

第二、归档解档存储方式

这个和Android中的对象序列化非常类似,这里对于需要归档解档的类必须要实现一个NSCoding协议。然后在实现编码和解码的方法即可。从这方面看他更像是Android中的Parcel类操作了。

第三、NSUserDefaults存储方式

这种方式操作最简单,和Android中的SharedPreferences方式非常类似,但是简单许多,可以保存多种类型的值,这里需要注意的几点是:

1、在保存集合类型数据的时候,操作的都是不可变类型集合

2、相同的key对应的值前者会被后者覆盖

3、可以保存NSData类型的值,这里就等同于对象的归档和解档操作了。

第四、plist文件存储方式

这种方式其实理论上不能算作是一种存储方式,但是他又比较特殊所以就单独拿出来介绍了,他是用来保存集合类型的数据,操作非常简单。

第五、数据库存储方式

这种方式可能是所有存储方式中最重要也是最复杂的,因为我们要编写sql语句,还的把指定类的内容保存到数据库中就得编写一大推代码了,但是操作不复杂就是代码可能会写的有点多,这个在Android也是类似的,除非使用第三方框架在精简操作。数据操作我们只需要遵循那么几步即可,开始打开数据库,建立表格,增删改查,最后操作完成之后记得关闭数据库。

项目下载:http://download.csdn.net/detail/jiangwei0910410003/9671504

 

五、总结

本文主要介绍了iOS中的一个重要模块就是数据存储方式,和Android中的存储方式唯一不一样的就是iOS中没有SD卡这种方式,其他的都与之类似,但是在操作过程中会发现比Android操作要简单的多了,了解了数据存储之后,后面会介绍多线程的知识点,这样离我们手动编写应用的目标越来越近了。继续努力吧!记得点个大大的赞啦!

《Android应用安全防护和逆向分析》

点击立即购买:京东  天猫

更多内容:点击这里

关注微信公众号,最新技术干货实时推送

编码美丽技术圈
微信扫一扫进入我的”技术圈”世界
扫一扫加小编微信
添加时请注明:“编码美丽”非常感谢!

转载请注明:尼古拉斯.赵四 » iOS狂暴之路—iOS中应用的数据存储方式解析

喜欢 (11)or分享 (0)