从本地设备测试中,我看到将文件写入 iOS 文件系统(无论您使用的调用级别如何)通常会在文件完全提交到闪存之前返回成功。这意味着,如果您硬重置设备然后重新启动,您的文件可能会回滚(如果写入完成或是原子的)或损坏。这种延迟的原因是什么(感谢文档,我找不到任何东西),有没有办法在实际文件系统写入完成时获得反馈。例如,我想确认从远程服务器接收并存储了一条数据,但我发现在写入“报告”成功后确认它可能会在发生硬崩溃或电源故障时导致数据丢失。
1 回答
由于这是一个 4 年前的问题,我不仅会提供答案,还会提供我在寻找它时所走的路。
我在官方文档中找不到任何明确的解释:文件系统编程指南。性能提示部分只有一条线索。它指出:
应用程序可以调用带有 F_NOCACHE 标志的 BSD fcntl 函数来启用或禁用文件的缓存。有关此函数的更多信息,请参阅 fcntl。
启用该F_NOCACHE
标志并不能解决您所说的问题,但是,fcntl
方法手册指出有一个您可能会觉得有趣的选项:
F_FULLFSYNC 与 fsync(2) 执行相同的操作,然后要求驱动器将所有缓冲数据刷新到永久存储设备
(从man fcntl
,见这里)。
我已经检查了手册以fsync
获取更多详细信息。最终,它给了我对问题和解决方案的最清晰和最容易理解的解释:
请注意,虽然 fsync() 会将所有数据从主机刷新到驱动器(即“永久存储设备”),但驱动器本身可能在相当长的一段时间内不会将数据物理写入盘片,并且可能会被写入输出序序列。
具体来说,如果驱动器断电或操作系统崩溃,应用程序可能会发现只有部分数据或没有数据被写入。磁盘驱动器还可以重新排序数据,以便以后的写入可能存在,而较早的写入不存在。
这不是理论上的边缘情况。这种情况很容易在现实世界的工作负载和驱动器电源故障中重现。
对于需要更严格保证数据完整性的应用程序,Mac OS X 提供了 F_FULLFSYNC fcntl。F_FULLFSYNC fcntl 要求驱动器将所有缓冲数据刷新到永久存储。需要严格的写入顺序的应用程序(例如数据库)应使用 F_FULLFSYNC 以确保其数据按预期顺序写入。
(从man fsync
,见这里)。
是的,这绝对不是理论上的边缘案例。值得庆幸的是,一旦您知道问题所在,解决方案就很简单了:
let filePath: String = "your file path"
// you can use other option than read-write
let fd = open(String(path.utf8), O_RDWR)
// if fd is -1, there was an error opening file, handle it as you wish
guard fd != -1 else { return }
// syncResult is -1 if sync operation failed, handle it as you wish
let syncResult = fcntl(fd, F_FULLFSYNC)
// don't forget to close opened file
close(fd)
完成后,您的数据将fcntl
被保存。
请注意,此操作比通常写入文件(通过NSFileManager
或writeToURL
方法系列)要慢。如果出现性能问题,最好将写入移至后台线程。