DiskFree 0.6.4

DiskFree 0.6.4

Arseniy Banayev维护。



DiskFree 0.6.4

Disk

Platform: iOS 9.0+ Language: Swift 4 Carthage compatible License: MIT

安装使用调试开发者的话文档使用Disk的应用许可贡献有问题吗?

Disk是一个根据Apple的iOS数据存储指南构建的强大且简单易用的文件管理库。Disk充分利用了Swift 4中引入的Codable协议,让您能够持久化struct而无需担心编码和解码。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框架嵌入到您的项目中

并在您想使用它的文件中导入import Disk

使用

Disk目前支持以下类型的数据持久化

  • Codable
  • [Codable]
  • 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

仅临时使用的数据应存储在《》/tmp目录中。尽管这些文件并未备份到iCloud,但在完成使用后记得删除这些文件,以免它们继续占用用户设备的空间。

单个设备上的多个应用程序可以访问共享目录,前提是这些应用程序在《com.apple.security.application-groups》权限数组的groupIdentifier中具有相同的标识符,具体请参阅《将应用添加到应用组]>在《权限键引用》中描述。

更多信息,请访问文档:《https://developer.apple.com/documentation/foundation/nsfilemanager/1412643-containerurlforsecurityapplicati


在符合所有这些要求和最佳实践的情况下,适当使用iOS文件系统可能会很困难,这就是磁盘出现的原因。磁盘使得遵循这些繁琐的规则变得简单有趣。

磁盘通过throw来处理错误。请参阅《处理错误使用Do-Catch》。

假设我们有一个名为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时,磁盘会自动将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的建议,来自https://github.com/saoudrizwan/Disk/issues/4

磁盘还允许您将结构体或结构体数组附加到相同类型数据的文件中。

try Disk.append(newMessage, to: "messages.json", in: .caches)

注意:您可以将单个结构体附加到空文件中,但为了能够正确检索该结构体,您必须以数组的形式检索它。

使用自定义的JSONEncoderJSONDecoder 感谢@nixzhu@mecid,来自https://github.com/saoudrizwan/Disk/pull/16https://github.com/saoudrizwan/Disk/pull/28

在幕后,磁盘使用苹果的JSONEncoderJSONDecoder类来编码和解码原始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结构体需要磁盘首先在文件位置解码任何现有值,然后附加新值,最后将生成的结构体编码到该位置。

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视频数据,那么磁盘对数据的方法将帮助您与文件系统协同工作以持久化所有数据类型。

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)

数据数组

磁盘将数据对象的数组保存为文件夹中的文件,就像它保存图片数组一样。

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]

追加数据

追加数据数据数组与追加图片或图片数组类似—创建具有自动生成的名称的新文件并将它们添加到指定的文件夹中。

try Disk.append(newDataObject, to: "Folder/", in: .documents)

大文件

了解何时在后台线程上对文件系统进行工作是很重要的。Disk是< strong>同步的,这使您能够更多地控制文件系统上的读写操作。苹果公司表示,“因为文件操作涉及访问磁盘,异步地执行这些操作几乎总是首选。”

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

注意:这些变量返回可选的 Int,因为检索文件系统资源值可能会失败并返回 nil。然而,这种情况非常不可能发生,这种行为仅出于安全目的。

辅助方法

  • 清空整个目录
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) {
    // ...
}

调试

磁盘是 全面的,这意味着它不会将错误留给偶然。Disk 的几乎所有方法都会抛出错误,这些错误可能是代表 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 ?? "")
        """)
}

上述示例处理了处理文件系统时最常见的错误:删除不存在的文件。

开发者的话

经过8+年的iOS开发,我几乎尝试了所有可行的方法来实现数据持久化(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还将一些必要但单调的任务变得简单,例如清理缓存或临时目录(如Apple的iOS数据存储指南所要求)

try Disk.clear(.temporary)

Disk在工作方式上直接与文件系统交互,因此比其他如《NSKeyedArchiver》这样的持久化解决方案要快得多。最重要的是,Disk在抛出错误方面非常彻底,确保你明白问题发生的原因。

文档

按住Option并点击Disk的任何方法,即可查看详细文档。![文档](https://user-images.githubusercontent.com/7799382/29153708-e49f0842-7d43-11e7-8eb3-4b2d13b56b70.png)

使用Disk的应用

许可证

Disk使用MIT许可证。如果您有任何问题或想分享您如何使用Disk,请提交问题。

贡献

请随意创建功能请求的问题或发送任何您认为能补充Disk及其哲学的补充请求。

有什么问题吗?

您可以通过电子邮件 [email protected],或通过twitter @sdrzn联系我。如果您发现错误或想添加新功能,请创建一个 问题