《File System Programming Guide》

File System Basics

文件系统负责处理数据文件、应用程序以及与操作系统相关的文件的持久性保存。

在iOS 10.3版本之后,APFS(Apple File System)替代了原有的HFS+成为了默认的文件系统。

所有连接到设备上的磁盘,无论是物理意义上的还是通过网络连接的,都为文件系统贡献自己的存储空间,因为文件📃的数量可能会相当多,所以文件系统使用「目录」来创建了一种文件的层次结构。

文件系统有两个基本的原则:

  1. 应用程序无法向自己权限之外的文件进行「写」的操作。
  2. 在应用程序内部,不同的文件被期望放置在正确的位置。

iOS文件系统面向自己运行的应用程序。为了保持系统简单,iOS设备的用户无法直接访问文件系统,应用程序应遵循此惯例。

出于安全的考虑,iOS与文件系统的交互被限制在了自己的「沙盒」目录中。

ios_app_layout_2x

如上图所示,应用程序通常被禁止访问(或创建目录)自己的文件目录之外的文件,如上图所示。但访问「联系人」或「音乐」是一个例外。

Bundle是文件系统中的一个目录,它将可执行代码、图像、声音、NIB文件、私有框架和库、插件、可加载包或任何其他类型的代码或资源集中在一个位置。

Directory Discription
Name.app 应用程序捆绑包,包含程序运行所需的全部必要资源,签名机制防篡改,无法写入。此目录的内容不会由iTunes或iCloud备份。
Documents/ 该目录应该只包含您可能希望向用户公开的文件,如绘画软件的画稿。该目录的内容由iTunes和iCloud备份。
Documents/Inbox 外部实体要求应用程序打开的文件。可以读取和删除此目录中的文件,但不能创建新文件或写入现有文件。此目录的内容由iTunes和iCloud备份。
Library/ 使用Library保存不想暴露给用户的任何文件。该目录通常包括 Application SupportCaches子目录;但也可以创建自定义子目录。库目录的内容(缓存子目录除外)由iTunes和iCloud备份。
tmp/ 使用此目录保存不需要在应用程序启动之间保留的临时文件。当应用程序不运行时,系统可能会清除该目录。此目录的内容不会由iTunes或iCloud备份。

将文件放置在正确的位置

为了防止备份到iCloud / iTunes的时间过长,需要将文件放置在合适的位置上。

  • 将应用程序创建的支持文件放在Library/Application support/目录中。一般来说这些文件是需要向用户隐藏的,比如 data files, configuration files, templates

  • Documents/Application Support/ 是默认备份的,可以在调用-[NSURL setResourceValue:forKey:error:] 方法时传入NSURLIsExcludedFromBackupKey 键来指定不需要备份的文件。原则上所有可以被重新创建的 / 下载的文件都需要被排除。

  • 缓存数据保存在Library/Caches/ 中,缓存数据保存时间要长于tmp/,缓存数据并不影响程序正常运行,但可以提高运行的性能。一些需要网络上下载的内容可以保存在这里。

iCloud

iCloud提供了另一种结构化的文件存储方式:

  • 应用程序具有用于存储其本机文件的主iCloud容器目录。还可以访问其应用程序权利中列出的辅助iCloud容器目录。
  • iCloud中的文件备份成「documents」和「data」两部分,所有「documents」中的数据可以通过UI暴露给用户。

任何不能向用户暴露的文件都不应该放在「documents」中,而是应该在「container」中新建一个子文件夹。

访问文件或目录

访问文件或目录有两种方式:URL or String-Based Path

不同文件的访问方式:Resource Programming Guide. & String Programming Guide. & Event-Driven XML Programming Guide. & Archives and Serializations Programming Guide. & Bundle Programming Guide

官方推荐使用URLs来访问文件。

Path-based URL: file://localhost/Users/steve/Documents/MyFile.txt

File reference URL: file:///.file/id=6571367.2773272/

String-based path: /Users/steve/Documents/MyFile.txt

可以通过NSURL类来创建URL对象,需要的时候也可以将其转换为reference URL。

NSURLAboutteString方法可以将NSURL对象转换成NSString对象

必须先创建指向不存在的文件或目录的路径,然后才能在磁盘上创建它。

1
let music = Bundle.main.url(forResource: "童话镇", withExtension: ".m4r")

如果要访问捆绑包内的文件,首先需要在项目的Build Phase中的Copy Bundle Resources中手动添加资源,然后通过上面👆这行代码访问。

可以使用FileManager的单例defaulturls方法来创建一个特定位置的URL:

1
2
3
fileManager.urls(for: .libraryDirectory, in: .localDomainMask)

// [file:///Library/]

这段代码我现在的理解是在某个域中搜索某个位置,返回一个该位置的url,举个栗子🌰,比方说上面这段代码的输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fileManager.urls(for: .cachesDirectory, in: .localDomainMask)

// [file:///Library/Caches/]

fileManager.urls(for: .cachesDirectory, in: .localDomainMask))

// [file:///Library/Application%20Support/]

fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask))

// [file:///Users/qiu/Library/Developer/CoreSimulator/Devices/EDB9FB8F-2A38-4529-BE4D-B1BFBBB9AE80/data/Containers/Data/Application/E187EFA7-0FF9-495D-8309-675C53DE990C/Library/Application%20Support/]

// 这里有点稍微理解了不同的域搜索结果🔍是不同的,userDomainMask给我的感觉就是在这个用户的所有文件下搜索,而.localDomainMask就是在当前APP的本地目录下搜索吧! yes!

//大胆猜测,allDomainsMask就是在所有用户路径下搜索。至少现在来看跟userDomainMask的结果是一样的

//总之返回的结构都是URL就好了

遍历目录:

深搜索🔍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let resourceKeys = Set<URLResourceKey>([.nameKey, .isDirectoryKey])

let url = fileManager.urls(for: .cachesDirectory, in: .localDomainMask) // 这个就是获得某个目录下URL

let items = fileManager.enumerator(at: a.first!, includingPropertiesForKeys: Array(resourceKeys), options: .skipsHiddenFiles, errorHandler: nil)

for case let fileURL as URL in items! { 
  // 这里学了一下,for case let 的作用是「有条件的循环」,也就是说过滤掉了items中所有类型不是URL的case
  
  // 根据URL获取文件的指定信息。
    guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
          let isDirectory = resourceValues.isDirectory,
          let name = resourceValues.name
    else { continue }

    print(name, isDirectory)
}

// 这里有一个疑问,为什么我的两首歌都存在了 /cache/music路径下,使用上面的代码还是能够直接把歌查出来,难道这个遍历是递归的?

// 已经基本印证了我的猜想,就是重复递归所有子文件夹,如果想要只递归当前目录,应该用:fileManager.contentsOfDirectory

浅搜索🔍:

1
2
3
4
5
6
7
8
9
10
//读取音乐列表
func getMusicList() {
    //读取、查看文件用NSFileManager
    do {
        musicList = try fileManager.contentsOfDirectory(atPath: musicPath!)
        print("查询成功:\(musicList)")
    } catch let err as NSError {
        print("查询失败:\(err.localizedFailureReason)")
    }
}

看到这里,总之就是,基本上所有跟文件有关系的操作,都找FileManager.default就好了,包括查找,创建,移动,复制,删除,等等,FileManager.default还有一个FileManagerDelegate?类型的属性delegate,里面包括了一些对移动、复制等操作的响应时间,默认都是optional类型的方法。

‼️一个补充知识:

1
2
3
class var `default`: FileManager

// 此方法始终表示相同的文件管理器对象。如果您计划在文件管理器中使用委托来接收基于文件的操作已完成的通知,则应(使用init方法)创建一个FileManager的新实例,而不是使用Shared对象。

一些常用方法:

最后列举几个比较常见的操作吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func createDirectory(at: URL, withIntermediateDirectories: Bool, attributes: [FileAttributeKey : Any]?)
// Creates a directory with the given attributes at the specified URL.

func removeItem(at: URL)
// Removes the file or directory at the specified URL.


func replaceItemAt(URL, withItemAt: URL, backupItemName: String?, options: FileManager.ItemReplacementOptions) -> URL?
// Replaces the contents of the item at the specified URL in a manner that ensures no data loss occurs.

func copyItem(at: URL, to: URL)
// Copies the file at the specified URL to a new location synchronously.

func moveItem(at: URL, to: URL)
// Moves the file or directory at the specified URL to a new location synchronously.

‼️iCloud相关方法暂时略过

func url(for: FileManager.SearchPathDirectory, in: FileManager.SearchPathDomainMask, appropriateFor: URL?, create: Bool) -> URL
// Locates and optionally creates the specified common directory in a domain.

func urls(for: FileManager.SearchPathDirectory, in: FileManager.SearchPathDomainMask) -> [URL]
// Returns an array of URLs for the specified common directory in the requested domains.

线程安全

由于文件系统由所有正在运行的进程共享,因此当两个进程(或同一进程中的两个线程)尝试同时对同一文件执行操作时,可能会出现问题。

文件协调器(File coordinator)的工作是每当他们关心的文件被另一个进程或线程操作时通知相关方。

具体的以后再看吧,官方文档全是OC代码,看不懂。

TODO:

  • #### iCloud File Management

  • #### Managing Files and Directories

  • #### Techniques for Reading and Writing Files Without File Coordinators

  • #### Using FileWrappers as File Containers