如果你觉得 Bodega 很有价值,我将非常感谢你考虑赞助我的开源工作,这样我可以继续开发像 Bodega 这样的项目,来帮助像你一样的开发者。
作为一个在纽约出生和长大的人,我可以证明 bodegas(西班牙小商店) 是我们城市朴实的基础设施,而 Bodega 的目标也是如此。我们感谢 bodegas 为我们所做的一切,但正是它们的简单性和普遍性几乎让我们忘记了它们的存在。
Bodega 是一个基于 Actor 的库,最初是一个简单的缓存,基于从磁盘读取和写入文件,并具有极其简单的 API。如今,Bodega 提供了一种基础设施,任何应用程序的数据层都可以使用。无论你是想轻松存储 Codable 对象、构建缓存,还是与你的 API 或像 CloudKit 这样的服务交互,这一切都只需几行代码即可完成。
Bodega 的 StorageEngine
是实现各种可能性的核心。让任何数据库、持久层,甚至 API 服务器遵循 StorageEngine
协议,即可自动为你的应用程序提供一个极其简单的数据层,这要归功于 Bodega 的 ObjectStorage
。开发者不再需要与 Data
和数据库交互,而是与应用程序的 Swift 类型交互,无论这些类型是什么,都拥有统一的 API,并且开箱即用地处理并发。
Bodega 自身就完全可用且实用,但它也是 Boutique 的基础。你可以在 Boutique Demo 文件夹 中找到一个基于 Boutique 构建的演示应用程序,向你展示如何在短短几行代码中创建一个离线就绪的实时更新 SwiftUI 应用程序。你可以在这篇博文中阅读更多关于该架构背后的思考,其中探讨了 Boutique 和 Model View Controller Store 架构。
Bodega 为你提供了两种类型的存储原语:StorageEngine
和 ObjectStorage
。StorageEngine
将 Data
写入持久层,而 ObjectStorage
则处理符合 Codable
协议的 Swift 类型。StorageEngine
可以将项目保存到磁盘、SQLite,甚至你自己的数据库,而 ObjectStorage
在 StorageEngine
之上提供了一个统一层,为将对象保存到你选择的任何 StorageEngine
提供了单一 API。Bodega
默认提供 DiskStorageEngine
和 SQLiteStorageEngine
,你甚至可以基于你的应用程序服务器或像 CloudKit 这样的服务构建 StorageEngine
,如果你想要一种简单的方式来与你的 API 交互。你甚至可以组合存储引擎来创建一个复杂的数据管道,该管道访问你的 API 并将项目保存到数据库中,所有这些都在一个 API 调用中完成。可能性是无限的!
// Initialize a SQLiteStorageEngine to save data to an SQLite database.
let storage = SQLiteStorageEngine(
directory: .documents(appendingPath: "Quotes")
)
// Alternatively Bodega provides a DiskStorageEngine out of the box too.
// It has the same API but uses the file system to store objects. ¹
let storage = DiskStorageEngine(
directory: .documents(appendingPath: "Quotes")
)
// CacheKeys can be generated from a String or URL.
// URLs will be reformatted into a file system safe format before writing to disk.
let url = URL(string: "https://redpanda.club/dope-quotes/dolly-parton")
let cacheKey = CacheKey(url: url)
let data = Data("Find out who you are. And do it on purpose. - Dolly Parton".utf8)
// Write data to disk
try await storage.write(data, key: cacheKey)
// Read data from disk
let readData = await storage.read(key: cacheKey)
// Remove data from disk
try await storage.remove(key: Self.testCacheKey)
¹ SQLiteStorageEngine
与 DiskStorageEngine
的权衡在StorageEngine 文档中讨论,但 SQLiteStorageEngine
是建议的默认选项,因为它具有远超后者的性能,并且使用相同的简单 API。
Bodega 开箱即用地提供了两个不同的 StorageEngine
实例,但如果你想构建自己的实例,你所要做的就是遵循 StorageEngine
协议。这将允许你为任何数据层创建一个 StorageEngine
,无论你是想构建 CoreDataStorageEngine
、RealmStorageEngine
、KeychainStorageEngine
,还是甚至映射到你的 API 的 StorageEngine
。如果你可以读取、写入或删除数据,你就可以遵循 StorageEngine
协议。
public protocol StorageEngine: Actor {
func write(_ data: Data, key: CacheKey) async throws
func write(_ dataAndKeys: [(key: CacheKey, data: Data)]) async throws
func read(key: CacheKey) async -> Data?
func read(keys: [CacheKey]) async -> [Data]
func readDataAndKeys(keys: [CacheKey]) async -> [(key: CacheKey, data: Data)]
func readAllData() async -> [Data]
func readAllDataAndKeys() async -> [(key: CacheKey, data: Data)]
func remove(key: CacheKey) async throws
func remove(keys: [CacheKey]) async throws
func removeAllData() async throws
func keyExists(_ key: CacheKey) async -> Bool
func keyCount() async -> Int
func allKeys() async -> [CacheKey]
func createdAt(key: CacheKey) async -> Date?
func updatedAt(key: CacheKey) async -> Date?
}
Bodega 最常与 Boutique 配对使用,但你也可以将其用作独立的缓存。任何 StorageEngine
都可以从你的持久层读取或写入 Data
,但 ObjectStorage
提供了处理 Swift 类型的功能,只要它们符合 Codable
协议。ObjectStorage
具有与 DiskStorage
非常相似的 API,但函数名称略有不同,以更明确地表明你正在处理对象而不是 Data
。
// Initialize an ObjectStorage object
let storage = ObjectStorage(
storage: SQLiteStorageEngine(directory: . documents(appendingPath: "Quotes"))!
)
let cacheKey = CacheKey("churchill-optimisim")
let quote = Quote(
id: "winston-churchill-1",
text: "I am an optimist. It does not seem too much use being anything else.",
author: "Winston Churchill",
url: URL(string: "https://redpanda.club/dope-quotes/winston-churchill")
)
// Store an object
try await storage.store(quote, forKey: cacheKey)
// Read an object
let readObject: Quote? = await storage.object(forKey: cacheKey)
// Grab all the keys, which at this point will be one key, `cacheKey`.
let allKeys = await storage.allKeys()
// Verify by calling `keyCount`, both key-related methods are also available on `DiskStorage`.
await storage.keyCount()
// Remove an object
try await storage.removeObject(forKey: cacheKey)
Bodega 作为与数据交互和持久化数据的原语非常有用,但当集成到 Boutique 中时,它会更加强大。Boutique 是一个 Store
,是互补的 Model View Controller Store
(MVCS) 架构的基础。MVCS 结合了你熟悉和喜爱的 MVC 架构 的熟悉性和简单性,以及 Store
的强大功能,为你的应用程序提供一个简单但定义明确的状态管理和数据架构。
如果你想了解更多关于其工作原理的信息,你可以在一篇 博文 中阅读关于该理念的内容,我在其中探讨了 SwiftUI 的 MVCS,你可以在这个 repo 中找到一个由 Boutique 驱动的离线就绪实时更新 MVCS 应用程序的参考实现。
本项目提供了多种形式的反馈传递给维护者。
如果你对 Bodega 有疑问,我们希望你首先查阅文档,看看你的问题是否已在那里得到解答。
如果你仍然有疑问、改进建议或改进 Bodega 的方法,本项目利用了 GitHub 的 Discussions 功能。
如果你发现错误并希望报告,我们将感谢你提交一个 issue。
Swift Package Manager 是一种用于自动化 Swift 代码分发的工具,并已集成到 Swift 构建系统中。
一旦你设置好你的 Swift 包,添加 Bodega 作为依赖项就像将其添加到你的 Package.swift
的 dependencies 值一样简单。
dependencies: [
.package(url: "https://github.com/mergesort/Bodega.git", .upToNextMajor(from: "1.0.0"))
]
如果你不喜欢使用 SPM,你可以通过复制文件手动将 Bodega 集成到你的项目中。
嗨,我是 Joe,在网络上到处都是,尤其是在 Mastodon 上。
查看许可证以获取关于如何使用 Bodega 的更多信息。
Bodega 是一项爱的劳动,旨在帮助开发者构建更好的应用程序,让你更容易释放你的创造力,为你自己和你的用户创造出色的东西。如果你觉得 Bodega 很有价值,我将非常感谢你考虑赞助我的开源工作,这样我可以继续开发像 Bodega 这样的项目,来帮助像你一样的开发者。
既然你已经了解了最新情况,让我们离线处理 📭