文件与存储
在iOS的开发过程中,常常会遇到数据持久化存储方面的问题,而数据持久化的基础是建立在iOS的沙盒机制上的,因此我们必须了解iOS的沙盒机制。
iOS中的沙盒机制(SandBox)是一种安全体系,它规定了应用程序只能在本应用的文件夹内读取文件,不可以访问其他区域的内容,此区域被称为沙盒。所有的非代码文件都保存在这个地方,比如图片、声音、属性列表和文本文件等。需要注意,每个应用程序都有属于本应用的沙盒存储空间。应用程序不能随意跨越自己的沙盒去访问其他的应用程序沙盒的内容。

详细可查看官方文档:File System Basics
沙盒目录
默认情况下,每个应用的沙盒包含3个文件夹:Documents、Library和tmp以及一个.app包。由于沙盒机制的存在,应用只能在这几个目录中读写文件。使用iTunes与iPhone同步时,会备份所有的Documents和Library文件。iPhone在重启时,会丢弃所有的tmp文件。
Documents:存储用户数据。苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录,还可通过配置实现iTunes共享文件
Library:其中包含Caches文件夹和Preferences文件夹。用来存放默认设置或其它状态信息
Library/Caches:存放缓存文件,即不需要备份的非重要数据,譬如网络数据。iTunes不会备份此目录,但在应用退出时此目录下的文件并不会被删除
Library/Preferences:保存应用程序偏好设置的
plist文件。会被 iTunes 同步备份
tmp:存放创建的临时文件,退出程序时,可能被系统删除。
附:iOS应用常用目录
| 目录 | Description |
|---|---|
AppName.app |
这是该应用程序的捆绑包。此目录包含应用程序及其所有资源。无法写入此目录。为了防止篡改,bundle目录在安装时被签名。对这个目录的写入会改变签名,并阻止你的应用程序启动。然而,你可以获得对存储在应用程序包中的任何资源的只读访问。该目录的内容不会被iTunes或iCloud备份。然而,iTunes确实对任何从App Store购买的应用程序进行了初始同步。 |
Documents/ |
使用此目录来存储用户生成的内容。 该目录的内容可以通过文件共享提供给用户; 因此,此目录应仅包含您可能希望向用户公开的文件。此目录的内容由 iTunes 和 iCloud 备份。 |
Documents/Inbox |
使用此目录来访问你的应用程序被外部实体要求打开的文件。 具体来说,邮件程序将与你的应用程序相关的电子邮件附件放在此目录中。 文档交互控制器也可以在其中放置文件。你的应用程序可以读取和删除此目录中的文件,但不能创建新文件或写入现有文件。 如果用户尝试编辑此目录中的文件,你的应用程序必须在进行任何更改之前将其悄悄移出目录。此目录的内容由 iTunes 和 iCloud 备份。 |
Library/ |
这是任何不属于用户数据文件的顶级目录。你通常把文件放在几个标准的子目录中。iOS应用程序通常使用Application Support和Caches子目录;但是,你可以创建自定义的子目录。使用Library子目录存放任何你不想暴露给用户的文件。你的应用程序不应该将这些目录用于用户数据文件。Library目录的内容(Caches 子目录除外)由iTunes和iCloud备份。关于Library目录及其常用子目录的其他信息,请参阅 The Library Directory Stores App-Specific Files |
tmp/ |
使用此目录来写入临时文件,这些文件在你的应用程序的启动之间不需要持续存在。你的应用程序应该在不再需要时从这个目录中删除文件;但是,当你的应用程序不运行时,系统可能会清除这个目录。这个目录的内容不会被iTunes或iCloud备份。 |
获取路径
Foundation框架中提供了快速获取沙盒相关路径的方法和函数
// 获取沙盒主目录路径
NSString *homeDir = NSHomeDirectory();
// 获取Documents路径
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
// 获取Library 路径
NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
// 获取Caches 路径
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 获取tmp 路径
NSString *tmpDir = NSTemporaryDirectory();
NSSearchPathForDirectoriesInDomains函数用于查找目录,返回指定范围内的指定名称的目录的路径集合。原型:
NSArray<NSString *> * NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
有三个参数:
directory:NSSearchPathDirectory类型的枚举值,表明要搜索的目录名称,比如用-NSDocumentDirectory表明要搜索的是Documents目录domainMask:NSSearchPathDomainMask类型的枚举值,指定搜索范围,这里的NSUserDomainMask表示搜索的范围限制于当前应用的沙盒目录。expandTilde:BOOL值,表示是否展开波浪线。该值为YES即表示写成全写路径形式,为NO就表示写成~。譬如设置为NO,则Caches路径就是~/Library/Caches
Flutter 的处理
这部分内容,我们在《Flutter全栈式开发》课程中已经学习过了,这里结合原生知识回顾一下。
某些时候需要下载或保存文件到手机本地,但由于iOS和Android的文件机制存在很大差别,这时候就需要有一个统一的接口去处理文件的路径问题。在Flutter上我们可以使用插件库 path_provider
该插件库支持访问两个文件路径:
- 临时目录:一般用于缓存,系统可以随时清除。 在 iOS 上,这对应于
NSTemporaryDirectory()返回的值,在 Android 上,这是getCacheDir()回的值- 文档目录:应用程序的目录。用于存储只有本应用可访问的文件。 只有当应用程序被删除时,系统才会清除该目录。 在 iOS 上,对应
NSDocumentDirectory, 在 Android 上是 App Data目录
try {
/// 获取临时目录
var tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
debugPrint(tempPath);
/// 获取文档目录
var appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
debugPrint(appDocPath);
/// 获取真实路径后,即可通过Dart 标准库中的File来操作
File file = File(tempPath);
}catch(err) {
print(err);
}
Bundle
Bundle包是文件系统中的一个目录,它将可执行代码和相关资源(如图像和声音)集中在一起。在iOS和OS X中,应用程序、框架、插件和其他类型的软件都是bundle包。bundle包是一个具有标准化分层结构的目录,其中包含可执行代码和该代码使用的资源。
这里的可执行是指编译过后可直接运行的二进制。一个典型的例子就是iOS程序打包后的ipa,我们解压ipa后会得到一个payload文件夹,进入文件夹之后会发现一个和ipa同名的.app文件夹。这个.app文件夹就是一个Bundle。注意,IPA(iPhone Application的缩写)实质是一个 zip压缩包,真正的程序包是.app文件夹。
大多数类型的Xcode项目在构建可执行文件时为你创建一个bundle。因此,你很少需要手工构建一个bundle包。即便如此,了解它们的结构以及如何访问其中的代码和资源还是很重要的。
Bundle的结构和内容
bundle可以包含可执行代码、图像、声音、nib文件、私有框架和库、插件、可加载包或任何其他类型的代码或资源。它还包含一个叫做信息属性列表(Info.plist)的运行时配置文件。这些项目中的每一项在bundle结构中都有其适当的位置。诸如图像、声音和nib文件等资源被存放在Resources子目录中。它们可以是本地化的,也可以是非本地化的。本地化的文件(包括字符串文件,它是本地化字符串的集合)被放在Resources的子目录中,其扩展名为lproj,名称对应于一种语言,可能还有一个区域设置。

详细的Bundle包结构内容,请查看此文档
访问Bundle资源
每个应用程序都有一个主bundle,它是包含应用程序代码的包。当用户启动一个应用程序时,它会在主bundle中找到它立即需要的代码和资源,并将它们加载到内存中。此后,应用程序可以根据需要动态地(和延迟地)从主bundle或下级bundle中加载代码和资源。
NSBundle类和Core Foundation的CFBundleRef不透明类型为你的应用程序提供了定位bundle资源的方法。在Objective-C中,你首先必须获得一个NSBundle的实例,与物理bundle相对应。要获得一个应用程序的主bundle,可以调用类方法mainBundle。其他NSBundle方法在给定文件名、扩展名和(可选)bundle子目录时,返回bundle资源的路径。当你有了一个资源的路径后,可以使用适当的类将其加载到内存中。
// 设置文件路径
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"SourcesBundle" ofType:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithPath:bundlePath];
// 加载 nib 文件
UINib *nib = [UINib nibWithNibName:@"BundleDemo" bundle:resourceBundle];
公众号“编程之路从0到1”