安装 • 用法 • 调试 • 开发者的话 • 文档 • 使用 Disk 的应用程序 • 许可证 • 贡献 • 问题?
Disk 是一个基于 Apple 的 iOS 数据存储指南 构建的高效且简单的文件管理库。Disk 利用 Swift 4 中引入的 Codable
协议的优势,让您能够轻松持久化结构体,不必担心编码/解码。此外,Disk 还可以帮助您用一行代码将图片和其他数据类型保存到磁盘。
兼容性
Disk 需要 iOS 9+ 且与使用 Swift 4.0 及以上版本的项目兼容。因此,您在工作中使用 Disk 时必须使用至少 Xcode 9。
安装
Disk 支持 CocoaPods 1.7.0 的新多 Swift 功能,适用于 Swift 4.0、4.2 和 5.0。只需在 Podfile 中指定 supports_swift_versions
即可。
platform :ios, '9.0'
target 'ProjectName' do
use_frameworks!
supports_swift_versions '< 5.0' # configure this for your project
pod 'Disk', '~> 0.6.4'
end
(如果遇到问题,请运行 pod repo update
并重试)
github "saoudrizwan/Disk"
dependencies: [
.Package(url: "https://github.com/saoudrizwan/Disk.git", "0.6.4")
]
- 或者将 Disk 框架嵌入到您的项目中
并在您想使用 Disk 的文件中导入 import Disk
。
使用
磁盘目前支持以下类型的持久化
可编码
[可编码]
UIImage
[UIImage]
Data
[Data]
这些通常是您在iOS上需要的唯一需要持久化的类型。
磁盘遵循Apple的iOS数据存储指南,因此允许您在四个主要目录和共享容器中保存文件
.documents
文档目录 只有那些用户生成的或其他不可由您的应用程序重新创建的数据,才应存储在 <Application_Home>/Documents 目录中,并且将被iCloud自动备份。
.caches
缓存目录 应该存储在 <Application_Home>/Library/Caches 目录中的数据是那些可以再次下载或重新生成的数据。例如,数据库缓存文件和像杂志、报纸和地图应用程序那样的可下载内容。
请使用此目录来写入任何您想在应用程序启动之间或更新期间持久化的应用程序特定支持文件。《您的应用程序通常负责添加和删除这些文件》(参见辅助方法)。它还应能够根据需要重新创建这些文件,因为iTunes在设备的完全恢复过程中会删除它们。在iOS 2.2及以后版本中,此目录的内容不会被iTunes备份。
请注意,系统可能会删除Caches/目录以释放磁盘空间,因此您的应用程序必须能够在需要时重新创建或下载这些文件。
.applicationSupport
应用程序支持目录 将应用创建的支持文件放置在 <Application_Home>/Library/Application support 目录中。通常,该目录包含应用运行时使用的文件,但这些文件应保持对用户隐藏。此目录还包括数据文件、配置文件、模板以及从应用包加载的资源修改版本。
.temporary
临时目录 仅临时使用的数据应存储在 <Application_Home>/tmp 目录中。尽管这些文件不会被备份到iCloud,但请注意在完成时删除这些文件,以避免它们继续占用用户设备的存储空间。
.sharedContainer(appGroupName: String)
应用组共享容器 在单个设备上的多个应用可以访问共享目录,前提是这些应用在 com.apple.security.application-groups
授权数组中具有相同的 groupIdentifier
,具体请参见 Entitlement Key Reference 中的 Adding an App to an App Group。
更多信息,请参阅文档: https://developer.apple.com/documentation/foundation/nsfilemanager/1412643-containerurlforsecurityapplicati
根据所有这些要求和最佳实践,正确地处理iOS文件系统可能会很困难,这就是为什么磁盘出现的原因。磁盘使遵循这些繁琐的规则变得简单而有趣。
使用Disk很简单。
Disk通过throw
来处理错误。请参阅 Handling Errors Using Do-Catch。
Codable
)的规则
结构体(必须遵循 假设我们有一个名为 Message
的数据模型...
struct Message: Codable {
let title: String
let body: String
}
... 然后我们想要将一条消息持久化到磁盘...
let message = Message(title: "Hello", body: "How are you?")
try Disk.save(message, to: .caches, as: "message.json")
... 或者我们可能想要将它保存在一个文件夹里...
try Disk.save(message, to: .caches, as: "Folder/message.json")
... 或者稍后检索这条消息...
let retrievedMessage = try Disk.retrieve("Folder/message.json", from: .caches, as: Message.self)
如果你使用 Option + 点击 retrievedMessage
,那么 Xcode 会显示它的类型为 Message
。相当酷,对吧?
那么,后面发生了什么?磁盘首先将 message
转换为 JSON 数据,并将其 原子性地写入到新创建的文件中,该文件的路径为 <Application_Home>/Library/Caches/Folder/message.json
。然后当我们检索 message
时,Disk 会自动将 JSON 数据转换为我们的 Codable
结构体类型。
结构体数组的处理呢?
感谢 Codable
的强大功能,存储和检索结构体数组正如上述代码一样简单。
var messages = [Message]()
for i in 0..<5 {
messages.append(Message(title: "\(i)", body: "..."))
}
try Disk.save(messages, to: .caches, as: "messages.json")
let retrievedMessages = try Disk.retrieve("messages.json", from: .caches, as: [Message].self)
追加结构体 (感谢 @benpackard 的建议)
Disk 还允许您将结构体或结构体数组追加到包含相同数据类型的数据文件中。
try Disk.append(newMessage, to: "messages.json", in: .caches)
注意:您可以将单个结构体追加到空文件中,但为了能够正确地再次检索该结构体,您必须将其作为数组检索。
使用自定义 JSONEncoder
或 JSONDecoder
(感谢 @nixzhu 和 @mecid)
在幕后,Disk 使用苹果的 JSONEncoder
和 JSONDecoder
类来编码和解码原始 JSON 数据。如果您需要特殊的编码或解码策略,您可以使用这些类的自定义实例。
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
try Disk.save(messages, to: .caches, as: "messages.json", encoder: encoder)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let retrievedMessages = try Disk.retrieve("messages.json", from: .caches, as: [Message].self, decoder: decoder)
注意:追加一个 Codable
结构体需要 Disk 首先解码文件位置的任何现有值,追加新值,然后将结果结构编码到该位置。
try Disk.append(newMessage, to: "messages.json", in: .caches, decoder: decoder, encoder: encoder)
图片
let image = UIImage(named: "nature.png")
try Disk.save(image, to: .documents, as: "Album/nature.png")
let retrievedImage = try Disk.retrieve("Album/nature.png", from: .documents, as: UIImage.self)
图片数组
多张图片将保存到一个新的文件夹中。每张图片将被重命名为 0.png、1.png、2.png 等。
var images = [UIImages]()
// ...
try Disk.save(images, to: .documents, as: "FolderName/")
您不需要在文件夹名后包含 "/",但这样做可以表明您不是将要把所有图片数据写入一个文件,而是作为几个文件写入一个新文件夹。
let retrievedImages = try Disk.retrieve("FolderName", from: .documents, as: [UIImage].self)
假设您这样保存了一堆图片到一个文件夹中
try Disk.save(deer, to: .documents, as: "Nature/deer.png")
try Disk.save(lion, to: .documents, as: "Nature/lion.png")
try Disk.save(bird, to: .documents, as: "Nature/bird.png")
并且您甚至可能在 Nature 文件夹中保存了一个 JSON 文件
try Disk.save(diary, to: .documents, as: "Nature/diary.json")
然后您就可以这样检索 Nature 文件夹中的所有图片
let images = try Disk.retrieve("Nature", from: .documents, as: [UIImage].self)
... 将返回 -> [deer.png, lion.png, bird.png]
添加图片
与单纯修改现有JSON文件的添加结构体不同,添加图片会将该图片作为独立文件添加到文件夹中。
try Disk.append(goat, to: "Nature", in: .documents)
注意:为了指定图片文件名称以便稍后检索,建议手动使用 save(:to:as:)
函数保存独立图片。使用 append(:to:in:)
函数将导致创建一个具有自动生成名称的文件(例如,如果您将图片添加到已包含图片的文件夹(0.png, 1.png, 2.png)中,则新图片将命名为 3.png)。如果图片名称不重要,则可以使用 append(:to:in:)
。添加图片数组的行为类似。
数据
如果您正在尝试保存像.mp4视频数据这样的数据,Disk的Data
对象方法将帮助您与文件系统交互以持久化所有数据类型。
let videoData = Data(contentsOf: videoURL, options: [])
try Disk.save(videoData, to: .documents, as: "anime.mp4")
let retrievedData = try Disk.retrieve("anime.mp4", from: .documents, as: Data.self)
Data
数组
Disk会将Data
对象数组作为文件保存到文件夹中,就像图像数组一样。
var data = [Data]()
// ...
try Disk.save(data, to: .documents, as: "videos")
let retrievedVideos = try Disk.retrieve("videos", from: .documents, as: [Data].self)
如果您从带有图像和JSON文件的文件夹中检索[Data]
,则这些文件将包含在返回值中。接着上一个例子中的图像数组部分
let files = try Disk.retrieve("Nature", from: .documents, as: [Data].self)
... 将返回 -> [deer.png, lion.png, bird.png, diary.json]
添加Data
添加Data
或Data
数组与添加图像或图像数组类似——新文件将使用自动生成的名称创建并添加到指定的文件夹。
try Disk.append(newDataObject, to: "Folder/", in: .documents)
大文件
了解在后台线程上何时与文件系统交互非常重要。Disk是同步的
,这使得您能够更好地控制对文件系统的读写操作。Apple提到,“由于文件操作涉及访问磁盘,因此几乎始终建议异步执行这些操作。”
Grand Central Dispatch是使用Disk异步操作的最佳方式。下面是一个示例
activityIndicator.startAnimating()
DispatchQueue.global(qos: .userInitiated).async {
do {
try Disk.save(largeData, to: .documents, as: "Movies/spiderman.mp4")
} catch {
// ...
}
DispatchQueue.main.async {
activityIndicator.stopAnimating()
// ...
}
}
不要忘记处理这些类型任务可能会被中断的情况。
iOS 11 存储信息
苹果在第204个会议中介绍了多个优秀的iOS存储实践,强调了iOS 11中增加的几个新的NSURL
卷容量细节。这些信息允许我们判断何时适合在用户的磁盘上存储数据。
- 总容量
Disk.totalCapacity
- 可用容量
Disk.availableCapacity
- 可用容量(重要使用)。这表示可以为用户在应用UI中明确请求的内容(例如,下载视频或游戏的新级别)腾出的空间。
Disk.availableCapacityForImportantUsage
- 可用容量(机会性使用)。这表示用户可能希望但要明确请求的内容的可用的空间(例如,他们观看的视频系列的下一集,或他们在服务器中最近更新的文档,他们可能希望打开)。
Disk.availableCapacityForOpportunisticUsage
注意:由于检索文件系统资源值可能会失败并返回nil
,因此这些变量返回的是Optional Int
。然而,这种情况非常不可能发生,这种行为仅出于安全目的存在。
辅助方法
- 清除整个目录
try Disk.clear(.caches)
- 删除文件/文件夹
try Disk.remove("video.mp4", from: .documents)
- 检查文件/文件夹是否存在
if Disk.exists("album", in: .documents) {
// ...
}
- 将文件/文件夹移动到另一个目录
try Disk.move("album/", in: .documents, to: .caches)
- 重命名文件/文件夹
try Disk.rename("currentName.json", in: .documents, to: "newName.json")
- 获取文件/文件夹的文件系统URL
let url = try Disk.url(for: "album/", in: .documents)
- 将文件/文件夹标记为
do not backup
属性(这将在低存储情况下保持文件/文件夹在磁盘中,但防止iCloud或iTunes对其进行备份。)
try Disk.doNotBackup("album", in: .documents)
您的应用主目录中的所有内容都已完成备份,但除外应用程序包本身、缓存目录和临时目录。
try Disk.backup("album", in: .documents)
(除非您确定无论用户的设备处于何种状态,都需要保留数据,否则您通常不应该使用.doNotBackup(:in:)
和.backup(:in:)
方法。)
URL
对应物
这些辅助方法中的大多数都有URL
对应物,以防您想直接使用文件的文件系统URL来工作。
let fileUrl = try Disk.url(for: "file.json", in: .documents)
- 删除文件/文件夹
try Disk.remove(fileUrl)
- 检查文件/文件夹是否存在
if Disk.exists(fileUrl) {
// ...
}
- 将文件/文件夹移动到另一个目录
let newUrl = try Disk.url(for: "Folder/newFileName.json", in: .documents)
try Disk.move(fileUrl, to: newUrl)
- 将文件/文件夹标记为
do not backup
属性
try Disk.doNotBackup(fileUrl)
try Disk.backup(fileUrl)
- 检查URL是否为文件夹
if Disk.isFolder(fileUrl) {
// ...
}
调试
磁盘是彻底的,这意味着它不会将错误留给偶然。磁盘的几乎所有方法都会因为代表Foundation
或值得您注意的Disk自定义错误而引发错误。
do {
if Disk.exists("posts.json", in: .documents) {
try Disk.remove("posts.json", from: .documents)
}
} catch let error as NSError {
fatalError("""
Domain: \(error.domain)
Code: \(error.code)
Description: \(error.localizedDescription)
Failure Reason: \(error.localizedFailureReason ?? "")
Suggestions: \(error.localizedRecoverySuggestion ?? "")
""")
}
上述示例解决了处理文件系统时最常见的错误:删除不存在的文件。
来自开发者的寄语
在iOS开发超过8年的时间里,我几乎尝试过所有可行的数据持久化方法(Core Data、Realm、NSKeyedArchiver、UserDefaults等),但除了NSKeyedArchiver之外几乎没有合适的。Swift 4发布后,我非常兴奋于Codable协议,因为我清楚它能为JSON编码提供哪些帮助。处理网络响应的JSON数据并将其转换为可用的结构从未如此简单。Disk的目标是扩展这种数据处理的简单性到文件系统。
假设我们从网络请求中获取了一些数据...
let _ = URLSession.shared.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
guard error == nil else { fatalError(error!.localizedDescription) }
guard let data = data else { fatalError("No data retrieved") }
// ... we could directly save this data to disk...
try? Disk.save(data, to: .caches, as: "posts.json")
}
}.resume()
...稍后再次以[Post]形式检索...
let posts = try Disk.retrieve("posts.json", from: .caches, as: [Post].self)
Disk消除了将数据编码到期望类型所需的许多繁琐的手动工作,并且做得很好。Disk还使得清理缓存或临时目录(如苹果的iOS数据存储指南中所需)等必要且单调的任务变得简单。
try Disk.clear(.temporary)
由于Disk直接与文件系统工作,因此在性能上也优于像NSKeyedArchiver这样的替代持久解决方案。最重要的是,Disk在抛出错误时会非常彻底,确保您可以理解问题发生的原因。
文档
使用Disk的应用程序
许可协议
磁盘使用MIT许可证。如果您有任何问题或想分享您如何使用磁盘,请提交问题。
贡献
请随时创建问题报告以提交功能请求或发送您认为可以补充磁盘及其理念的任何添加请求。
有问题吗?
通过电子邮件@example.com或通过twitter @sdrzn与我联系。如果您遇到bug或希望添加新功能,请创建问题报告。