
“我剥夺了核心数据,这是它应该运行的方式”
- 乔什·霍尔茨(Josh Holtz)
“精品店很容易实施,并使持久性变得轻而易举。这已成为我开始每个项目的第一个补充。
- 泰勒·希尔斯曼(Tyler Hillsman)
“精品店变得无价之宝,我现在在每个附带项目中都使用它。不必关心持久性是很棒的,而且入门的成本实际上是零。”
- 罗曼·普克莱特(Romain Pouclet)
如果您觉得精品店有价值,如果您考虑帮助赞助我的开源工作,我将非常感谢它,这样我就可以继续从事精品店等项目来帮助像您这样的开发人员。
精品店是一个简单但功能强大的持久库,是一小部分属性包装纸和类型,可为SwiftUI,Uikit和AppKit构建令人难以置信的简单状态驱动应用程序。凭借其双层内存 +磁盘缓存体系结构精品店提供了一种构建应用程序的方法,可以使用非常简单的API在几行代码中使用完整的离线存储进行实时更新。 Boutique是在Bodega顶部建造的,您可以在此存储库中找到一个模型视图控制器架构上构建的演示应用程序,该应用程序仅在几行代码中向您展示如何制作一个脱机的SwiftUi应用程序。您可以在此博客文章中探索MVCS架构中的架构背后的思维更多信息。
精品店只有一个您需要了解的概念。当您将数据保存到Store时,您的数据将自动持续为您,并作为常规的Swift数组暴露。 @StoredValue和@SecurelyStoredValue属性包装纸以相同的方式工作,但是它们没有一个数组,它们可以使用单数迅速的值。您将永远不必考虑数据库,应用程序中的所有内容都是常规的Swift阵列或使用应用程序模型的价值,其直接代码看起来像任何其他应用程序。
您可能会熟悉Redux或Composable Architecture的Store ,但是与那些框架不同,您不必担心添加操作或还原器。使用此Store实现,您的所有数据都会自动持续使用,无需其他代码。这使您可以以一种非常简单明了的方式构建具有完整离线支持的实时更新应用程序。
您可以在下面阅读精品店的高级概述,但是在这里也充分记录了精品店。
我们将在下面的Store进行高级概述,但是在此处,该Store已充分记录了上下文,用例和示例。
API的整个表面积用于在整个应用程序中实现完整的离线支持和实时模型更新,为三种方法, .insert() ,. .remove()和.removeAll() 。
// Create a Store ¹
let store = Store < Animal > (
storage : SQLiteStorageEngine . default ( appendingPath : " Animals " ) ,
cacheIdentifier : . id
)
// Insert an item into the Store ²
let redPanda = Animal ( id : " red_panda " )
try await store . insert ( redPanda )
// Remove an animal from the Store
try await store . remove ( redPanda )
// Insert two more animals to the Store
let dog = Animal ( id : " dog " )
let cat = Animal ( id : " cat " )
try await store . insert ( [ dog , cat ] )
// You can read items directly
print ( store . items ) // Prints [dog, cat]
// You also don't have to worry about maintaining uniqueness, the Store handles uniqueness for you
let secondDog = Animal ( id : " dog " )
try await store . insert ( secondDog )
print ( store . items ) // Prints [dog, cat]
// Clear your store by removing all the items at once.
store . removeAll ( )
print ( store . items ) // Prints []
// You can even chain commands together
try await store
. insert ( dog )
. insert ( cat )
. run ( )
print ( store . items ) // Prints [dog, cat]
// This is a good way to clear stale cached data
try await store
. removeAll ( )
. insert ( redPanda )
. run ( )
print ( store . items ) // Prints [redPanda]而且,如果您正在构建SwiftUi应用程序,则不必更改某事,就可以牢记Swiftui的精品店。 (但是当然可以在Uikit和Appkit中运作良好。)
// Since items is a @Published property
// you can subscribe to any changes in realtime.
store . $items . sink ( { items in
print ( " Items was updated " , items )
} )
// Works great with SwiftUI out the box for more complex pipelines.
. onReceive ( store . $items , perform : {
self . allItems = $0 . filter ( { $0 . id > 100 } )
} )Å您可以拥有想要的数量或很少的商店。为您在应用程序中下载的所有图像拥有一家商店可能是一个很好的策略,但是您可能还希望每种型号的商店都需要加速。您甚至可以为测试创建单独的商店,精品店不是规定性的,并且选择了您想建模数据的选择。您还会注意到,这是Bodega的一个概念,您可以在Bodega的StorageEngine文档中阅读。
²在添加或删除物品时,商店正在做的工作将所有更改保存到磁盘上。
³在SwiftUi中,您甚至可以用$items为View供电,并使用.onReceive()来更新和操纵商店$items发布的数据。
在技术上支持警告在精品店中存储图像或其他二进制数据,但不建议使用。原因是将图像存储在精品店可以在内存商店中膨胀,因此您的应用程序的内存。出于类似的原因,不建议将图像或二进制BLOB存储在数据库中,因此不建议将图像或二进制斑点存储在精品店中。
我们将在下面浏览@Stored属性包装器的高级概述,但是@Stored已在此处充分记录了上下文,用例和示例。
那很容易,但是我想向您展示一些使精品般的魔术。该Store是获得离线存储和实时更新的好处的简单方法,但是通过使用@Stored属性包装器,我们可以使用一行代码来缓存任何内存和磁盘上的任何属性。
extension Store where Item == RemoteImage {
// Initialize a Store to save our images into
static let imagesStore = Store < RemoteImage > (
storage : SQLiteStorageEngine . default ( appendingPath : " Images " )
)
}
final class ImagesController : ObservableObject {
/// Creates a @Stored property to handle an in-memory and on-disk cache of images. ⁴
@ Stored ( in : . imagesStore ) var images
/// Fetches `RemoteImage` from the API, providing the user with a red panda if the request succeeds.
func fetchImage ( ) async throws -> RemoteImage {
// Hit the API that provides you a random image's metadata
let imageURL = URL ( string : " https://image.redpanda.club/random/json " ) !
let randomImageRequest = URLRequest ( url : imageURL )
let ( imageResponse , _ ) = try await URLSession . shared . data ( for : randomImageRequest )
return RemoteImage ( createdAt : . now , url : imageResponse . url , width : imageResponse . width , height : imageResponse . height , imageData : imageResponse . imageData )
}
/// Saves an image to the `Store` in memory and on disk.
func saveImage ( image : RemoteImage ) async throws {
try await self . $images . insert ( image )
}
/// Removes one image from the `Store` in memory and on disk.
func removeImage ( image : RemoteImage ) async throws {
try await self . $images . remove ( image )
}
/// Removes all of the images from the `Store` in memory and on disk.
func clearAllImages ( ) async throws {
try await self . $images . removeAll ( )
}
}就是这样,就是这样。该技术的扩展非常好,并且在许多视图中共享此数据正是精品店在不增加API复杂性的情况下从简单到复杂的应用程序缩放。很难相信,现在您的应用程序可以通过完整的离线存储实时更新其状态,这要归功于一行代码。 @Stored(in: .imagesStore) var images
⁴(如果您希望将商店从视图模型,控制器或管理器对象分离,则可以将商店注入这样的对象。)
final class ImagesController : ObservableObject {
@ Stored var images : [ RemoteImage ]
init ( store : Store < RemoteImage > ) {
self . _images = Stored ( in : store )
}
}我们将在下面浏览@StoredValue和@SecurelyStoredValue属性包装器的高级概述,但在此处充分记录了它们的上下文,用例和示例。
由于大多数数据应用程序渲染以数组的形式出现,因此创建了Store和@Stored来存储数据数组。但是有时我们需要存储一个个人价值,这就是@StoredValue和@SecurelyStoredValue派上用场的地方。
无论您需要在下次启动应用程序时保存重要的信息,在钥匙扣中存储了一个auth令牌,还是想根据用户的设置更改应用程序的外观,这些应用程序配置都是您想要持续存在的单个值。
通常,人们会选择将类似的物品存储在UserDefaults中。如果您使用过@AppStorage则@StoredValue会感到宾至如归,它具有非常相似的API,并具有一些其他功能。 @StoredValue最终将存储在UserDefaults中,但它也会暴露出publisher ,因此您可以轻松地订阅更改。
// Setup a `@StoredValue has the same API.
@ StoredValue ( key : " hasHapticsEnabled " )
var hasHapticsEnabled = false
// You can also store nil values
@ StoredValue ( key : " lastOpenedDate " )
var lastOpenedDate : Date ? = nil
// Enums work as well, as long as it conforms to `Codable` and `Equatable`.
@ StoredValue ( key : " currentTheme " )
var currentlySelectedTheme = . light
// Complex objects work as well
struct UserPreferences : Codable , Equatable {
var hasHapticsEnabled : Bool
var prefersDarkMode : Bool
var prefersWideScreen : Bool
var spatialAudioEnabled : Bool
}
@ StoredValue ( key : " userPreferences " )
var preferences = UserPreferences ( )
// Set the lastOpenedDate to now
$lastOpenedDate . set ( . now )
// currentlySelected is now .dark
$currentlySelectedTheme . set ( . dark )
// StoredValues that are backed by a boolean also have a toggle() function
$hasHapticsEnabled . toggle ( ) @SecurelyStoredValue属性包装器可以做@StoredValue所做的一切,但与其在UserDefaults中存储值, @SecurelyStoredValue将在系统的键链中持续存在。这非常适合存储敏感值,例如密码或验证令牌,您不想将其存储在UserDefaults中。
如果您有任何疑问,我会问您先查看文档,精品店和Bodega都非常有记录。最重要的是,这家精品店没有一个,而是两个演示应用程序,每个应用程序都有不同的目的,而是说明如何构建精品店支持的应用程序。
当我构建V1时,我注意到那些喜欢精品店的人喜欢它,一旦他们理解如何使用它,人们认为这可能是好的,但越来越喜欢它。因此,我试图编写大量文档,解释构建iOS或MacOS应用时您会遇到的概念和常见用例。如果您仍然有疑问或建议,我对反馈非常愿意,那么在此读书中恰当地命名的反馈部分讨论了如何贡献。
精品店对仅使用几行代码构建实时脱机应用程序非常有用,但是当您使用我开发的Model View Controler存储架构时,它在上面的ImagesController中进行了演示时,它甚至更强大。 MVCS汇集了您所知道的MVC体系结构的熟悉和简单性,并借助Store的力量,为您的应用程序提供了简单但定义明确的状态管理和数据架构。
如果您想了解更多有关它的工作方式的信息,您可以在我探索SwiftUI的MVC的博客文章中阅读有关哲学的信息,并且可以在本仓库中找到由Boutique提供支持的脱机实时MVCS应用程序的参考实现。
我们只刮过了精品店在这里可以做什么的表面。利用Bodega的StorageEngine您可以构建复杂的数据管道,从缓存数据到与API服务器接口的所有功能。精品店和Bodega不仅仅是图书馆,它们是任何数据驱动应用程序的一组原始图,因此我建议给他们射击,玩演示应用程序,甚至构建自己的应用程序!
该项目提供了多种形式的向维护者提供反馈。
如果您对精品店有疑问,我们要求您首先咨询文档,以查看您的问题是否已在那里回答。
该项目已大量记录,但还包括多个示例项目。
StorageEngine则该项目将为您服务,以测试您需要构建的操作的性能。如果您仍然有一个问题,增强或改善精品店的方法,则该项目利用Github的讨论功能。
如果您发现一个错误并希望报告问题,将不胜感激。
Swift软件包管理器是自动化Swift代码分布的工具,并将其集成到Swift Build System中。
设置了Swift软件包后,将精品店添加为依赖项就像将其添加到Package.swift的依赖项值中一样容易。
dependencies: [
. package ( url : " https://github.com/mergesort/Boutique.git " , . upToNextMajor ( from : " 1.0.0 " ) )
] 如果您不想使用SPM,则可以通过复制文件将精品列入项目。
嗨,我在网上无处不在,尤其是在Mastodon上。
有关如何使用精品店的更多信息,请参见许可证。
精品店是一项热爱的劳动,旨在帮助开发人员构建更好的应用程序,从而使您更容易释放创造力并为自己和用户提供惊人的东西。如果您觉得精品店有价值,如果您考虑帮助赞助我的开源工作,我将非常感谢它,这样我就可以继续从事精品店等项目来帮助您像您这样的开发人员。
现在您知道了为您准备的东西,该开始开始了吗?