You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+67-49Lines changed: 67 additions & 49 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
### A simple but surprisingly fancy data store and so much more
4
4
5
-
>*"I ripped out Core Data, this is the way it should work"*
5
+
>*"I ripped out Core Data, this is the way it should work"*
6
6
7
7
— [Josh Holtz](https://github.com/joshdholtz)
8
8
@@ -32,7 +32,7 @@ Table of Contents
32
32
33
33
Boutique is a simple but powerful persistence library, a small set of property wrappers and types that enable building incredibly simple state-driven apps for SwiftUI, UIKit, and AppKit. With its dual-layered memory + disk caching architecture, Boutique provides a way to build apps that update in real time with full offline storage in only a few lines of code — using an incredibly simple API.
34
34
35
-
- Boutique is used in hundreds of apps, and I thoroughly test every API in my app [Plinky](https://plinky.app). (Which I highly recommend [downloading](https://plinky.app/download) and maybe even subscribing to. 😉)
35
+
- Boutique is used in hundreds of apps, and I thoroughly test every API in my app [Plinky](https://plinky.app). (Which I highly recommend [downloading](https://plinky.app/download) and maybe even subscribing to. 😉)
36
36
- Boutique is built atop [Bodega](https://github.com/mergesort/Bodega), and you can find a demo app built atop the Model View Controller Store architecture in this [repo](https://github.com/mergesort/Boutique/tree/main/Demo) which shows you how to make an offline-ready SwiftUI app in only a few lines of code. You can read more about the thinking behind the architecture in this blog post exploring the [MVCS architecture](https://build.ms/2022/06/22/model-view-controller-store).
37
37
38
38
---
@@ -90,7 +90,7 @@ try await store
90
90
.insert(dog)
91
91
.insert(cat)
92
92
.run()
93
-
93
+
94
94
print(store.items) // Prints [dog, cat]
95
95
96
96
// This is a good way to clear stale cached data
@@ -105,26 +105,41 @@ print(store.items) // Prints [redPanda]
105
105
And if you're building a SwiftUI app you don't have to change a thing, Boutique was made for and with SwiftUI in mind. (But works well in UIKit and AppKit of course. 😉)
106
106
107
107
```swift
108
-
// Since items is a @Published property
109
-
//you can subscribe to any changes in realtime.
110
-
store.$items.sink({ itemsin
111
-
print("Items was updated", items)
108
+
// Since @Store, @StoredValue, and @SecurelyStoredValue are `@Observable`, you can subscribe
109
+
//to changes in realtime using any of Swift's built-in observability mechanisms.
print("[Store Event: initial] Our Notes Store has initialized")
123
+
124
+
case .loaded:
125
+
print("[Store Event: loaded] Our Notes Store has loaded with notes", event.notes.map(\.text))
126
+
127
+
case .insert:
128
+
print("[Store Event: insert] Our Notes Store inserted notes", event.notes.map(\.text))
129
+
130
+
case .remove:
131
+
print("[Store Event: remove] Our Notes Store removed notes", event.notes.map(\.text))
132
+
}
133
+
}
134
+
}
118
135
```
119
136
---
120
137
121
-
¹ You can have as many or as few Stores as you'd like. It may be a good strategy to have one Store for all of the images you download in your app, but you may also want to have one Store per model-type you'd like to cache. You can even create separate stores for tests, Boutique isn't prescriptive and the choice for how you'd like to model your data is yours. You'll also notice, that's a concept from Bodega which you can read about in Bodega's [StorageEngine documentation](https://mergesort.github.io/Bodega/documentation/bodega/using-storageengines).
122
-
123
-
² Under the hood the Store is doing the work of saving all changes to disk when you add or remove items.
138
+
¹ You can have as many or as few Stores as you'd like. It may be a good strategy to have one Store for all of the notes you download in your app, but you may also want to have one Store per model-type you'd like to cache. You can even create separate stores for tests, Boutique isn't prescriptive and the choice for how you'd like to model your data is yours. You'll also notice, that's a concept from Bodega which you can read about in Bodega's [StorageEngine documentation](https://mergesort.github.io/Bodega/documentation/bodega/using-storageengines).
124
139
125
-
³ In SwiftUI you can even power your `View`s with `$items` and use `.onReceive()` to update and manipulate data published by the Store's `$items`.
140
+
² Under the hood the Store is doing the work of saving all changes to disk when you add or remove items.
126
141
127
-
> **Warning** Storing images or other binary data in Boutique is technically supported but not recommended. The reason is that storing images in Boutique's can balloon up the in-memory store, and your app's memory as a result. For similar reasons as it's not recommended to store images or binary blobs in a database, it's not recommended to store images or binary blobs in Boutique.
142
+
> **Warning** Storing binary blobs (such as images or Data) in Boutique is technically supported, but not recommended. The reason not to store images in Boutique is that storing images in Boutique's can balloon up the in-memory store, and your app's memory as a result. For similar reasons as it's not recommended to store images or binary blobs in a database, it's not recommended to store images or binary blobs in Boutique. Bodega provides a good solution for this problem, which you can read about in [this tutorial](https://mergesort.github.io/Bodega/documentation/bodega/building-an-image-cache/).
128
143
129
144
---
130
145
@@ -136,57 +151,60 @@ We'll go through a high level overview of the `@Stored` property wrapper below,
136
151
That was easy, but I want to show you something that makes Boutique feel downright magical. The `Store` is a simple way to gain the benefits of offline storage and realtime updates, but by using the `@Stored` property wrapper we can cache any property in-memory and on disk with just one line of code.
/// Saves an image to the `Store` in memory and on disk.
162
-
funcsaveImage(image: RemoteImage) asyncthrows {
163
-
tryawaitself.$images.insert(image)
176
+
177
+
/// Saves an note to the `Store` in memory and on disk.
178
+
funcsaveNote(note: Note) asyncthrows {
179
+
tryawaitself.$notes.insert(note)
164
180
}
165
-
166
-
/// Removes one image from the `Store` in memory and on disk.
167
-
funcremoveImage(image: RemoteImage) asyncthrows {
168
-
tryawaitself.$images.remove(image)
181
+
182
+
/// Removes one note from the `Store` in memory and on disk.
183
+
funcremoveNote(note: Note) asyncthrows {
184
+
tryawaitself.$notes.remove(note)
169
185
}
170
-
171
-
/// Removes all of the images from the `Store` in memory and on disk.
172
-
funcclearAllImages() asyncthrows {
173
-
tryawaitself.$images.removeAll()
186
+
187
+
/// Removes all of the notes from the `Store` in memory and on disk.
188
+
funcclearAllNotes() asyncthrows {
189
+
tryawaitself.$notes.removeAll()
174
190
}
175
191
}
176
192
```
177
193
178
-
That's it, that's really it. This technique scales very well, and sharing this data across many views is exactly how Boutique scales from simple to complex apps without adding API complexity. It's hard to believe that now your app can update its state in real time with full offline storage thanks to only one line of code. `@Stored(in: .imagesStore) var images`
194
+
That's it, that's really it. This technique scales very well, and sharing this data across many views is exactly how Boutique scales from simple to complex apps without adding API complexity. It's hard to believe that now your app can update its state in real time with full offline storage thanks to only one line of code. `@Stored(in: .notesStore) var notes`
179
195
180
196
---
181
197
182
-
⁴ (If you'd prefer to decouple the store from your view model, controller, or manager object, you can inject stores into the object like this.)
198
+
³ (If you'd prefer to decouple the store from your view model, controller, or manager object, you can inject stores into the object like this.)
183
199
184
200
```swift
185
-
finalclassImagesController: ObservableObject {
186
-
@Storedvar images: [RemoteImage]
201
+
@Observable
202
+
finalclassNotesController {
203
+
@ObservationIgnored
204
+
@Storedvar notes: [Note]
187
205
188
-
init(store: Store<RemoteImage>) {
189
-
self._images=Stored(in: store)
206
+
init(store: Store<Note>) {
207
+
self._notes=Stored(in: store)
190
208
}
191
209
}
192
210
```
@@ -252,7 +270,7 @@ As I was building v1 I noticed that people who got Boutique loved it, and people
252
270
253
271
### Further Exploration
254
272
255
-
Boutique is very useful on its own for building realtime offline-ready apps with just a few lines of code, but it's even more powerful when you use the Model View Controller Store architecture I've developed, demonstrated in the `ImagesController` above. MVCS brings together the familiarity and simplicity of the [MVC architecture](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html) you know and love with the power of a `Store`, to give your app a simple but well-defined state management and data architecture.
273
+
Boutique is very useful on its own for building realtime offline-ready apps with just a few lines of code, but it's even more powerful when you use the Model View Controller Store architecture I've developed, demonstrated in the `NotesController` above. MVCS brings together the familiarity and simplicity of the [MVC architecture](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html) you know and love with the power of a `Store`, to give your app a simple but well-defined state management and data architecture.
256
274
257
275
If you'd like to learn more about how it works you can read about the philosophy in a [blog post](https://build.ms/2022/06/22/model-view-controller-store) where I explore MVCS for SwiftUI, and you can find a reference implementation of an offline-ready realtime MVCS app powered by Boutique in this [repo](https://github.com/mergesort/MVCS).
258
276
@@ -304,7 +322,7 @@ If you prefer not to use SPM, you can integrate Boutique into your project manua
304
322
305
323
### About me
306
324
307
-
Hi, I'm [Joe](http://fabisevi.ch) everywhere on the web, but especially on [Mastodon](https://macaw.social/@mergesort).
325
+
Hi, I'm [Joe](http://fabisevi.ch) everywhere on the web, but especially on [Bluesky](https://bsky.app/profile/mergesort.me).
0 commit comments