
「私はコアデータを削除しました、これがそれが機能する方法です」
- ジョシュ・ホルツ
「ブティックは途方もなく実装が簡単で、永続性を簡単にします。それは、私が開始するすべてのプロジェクトへの私の最初の追加になりました。
- タイラーヒルズマン
「ブティックは非常に貴重になりました。私は今、あらゆるサイドプロジェクトでそれを使用しています。持続性を気にする必要はなく、始めるコストは実質的にゼロです。」
- ロマンプーレット
ブティックが貴重であると思うなら、私のオープンソースの仕事のスポンサーを支援することを検討してくれたら本当に感謝しています。
Boutiqueは、シンプルだが強力なPersistence Libraryであり、Swiftui、Uikit、Appkit用の非常にシンプルな状態駆動型アプリの構築を可能にするプロパティラッパーとタイプの小さなセットです。デュアルレイヤーメモリ +ディスクキャッシングアーキテクチャブティックは、非常にシンプルなAPIを使用して数行のコードで完全なオフラインストレージでリアルタイムで更新するアプリを構築する方法を提供します。 BoutiqueはBodegaの上に構築されており、このレポのモデルビューコントローラーストアアーキテクチャの上に構築されたデモアプリを見つけることができます。 MVCSアーキテクチャを探索するこのブログ投稿で、アーキテクチャの背後にある思考の詳細については、詳細を読むことができます。
ブティックには、理解する必要がある概念が1つしかありません。データをStoreに保存すると、データは自動的に持続し、通常のSwiftアレイとして公開されます。 @StoredValueと@SecurelyStoredValueプロパティラッパーは同じように機能しますが、配列の代わりに、単数の迅速な値で動作します。データベースについて考える必要はありません。アプリのすべては、他のアプリのように見える簡単なコードを使用して、アプリのモデルを使用して通常のSwiftアレイまたは値です。
ReduxやComposableアーキテクチャのStoreに精通している場合がありますが、これらのフレームワークとは異なり、アクションや還元剤を追加することを心配する必要はありません。このStore実装により、すべてのデータが自動的に持続するため、追加のコードは必要ありません。これにより、非常にシンプルで簡単な方法で、完全なオフラインサポートを備えたリアルタイム更新アプリを構築できます。
以下のブティックの高レベルの概要を読むことができますが、ブティックもここに完全に文書化されています。
以下のStoreの高レベルの概要を説明しますが、 Storeここでコンテキスト、ユースケース、および例で完全に文書化されています。
アプリ全体で完全なオフラインサポートとリアルタイムモデルの更新を達成するためのAPIの表面積全体は、 .insert() 、. .remove() 、および.removeAll() 3つの方法です。
// 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 } )
} )¹あなたはあなたが望むだけ多くまたは少数の店を持つことができます。アプリにダウンロードしたすべての画像に1つのストアを用意することは良い戦略かもしれませんが、キャッシュするモデルタイプごとに1つのストアが必要になる場合があります。テスト用に個別のストアを作成することもできます。ブティックは規範的ではなく、データをモデル化する方法の選択肢があります。また、これはボデガのコンセプトであり、ボデガのストレージエンジンドキュメントで読むことができます。
²フードの下で、店はアイテムを追加または削除するときにすべての変更をディスクに保存する作業を行っています。
³Swiftuiでは、 View Sを$itemsで動かすこともでき、 .onReceive()を使用して、ストアの$itemsによって公開されたデータを更新および操作することもできます。
ブティックに画像またはその他のバイナリデータの保存警告は技術的にサポートされていますが、推奨されません。その理由は、ブティックに画像を保存することで、メモリ内の店舗を膨らませることができ、結果としてアプリのメモリを膨らませることができます。同様の理由で、画像やバイナリブロブをデータベースに保存することをお勧めしないため、ブティックに画像やバイナリブロブを保存することはお勧めしません。
以下の@Storedプロパティラッパーの高レベルの概要について説明しますが、 @Stored 、コンテキスト、ユースケース、および例で完全に文書化されています。
それは簡単でしたが、ブティックを実に魔法のように感じさせる何かを見せたいです。このStore 、オフラインストレージとリアルタイムの更新の利点を獲得する簡単な方法ですが、 @Storedプロパティラッパーを使用することで、1つのコードのみでプロパティ内のプロパティとディスクでキャッシュできます。
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の複雑さを追加せずに、ブティックスケールを単純なアプリから複雑なアプリまでまさにどのようにスケーリングするかです。 1つのコードのみのおかげで、アプリが完全なオフラインストレージでリアルタイムで状態を更新できるとは信じがたいです。 @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が役立ちます。
次にアプリが起動するときに重要な情報を保存する必要があるかどうか、キーチェーンに認証トークンを保存するか、ユーザーの設定に基づいてアプリの外観を変更する必要があるかどうかにかかわらず、これらのアプリの構成は、持続したい個々の値です。
多くの場合、人々はそのような個々のアイテムを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に保存したくないパスワードやAuthトークンなどの敏感な値を保存するのに最適です。
ご質問がある場合は、最初にドキュメントをご覧ください。ブティックとボデガの両方が非常に重く文書化されています。そのブティックには、1つではなく2つのデモアプリが付属しており、それぞれが異なる目的を果たしていますが、ブティック支援アプリを構築する方法を示しています。
私がV1を構築していたとき、私はブティックを手に入れた人々がそれを愛していることに気づき、それが良いかもしれないと思ったが、それをどのように使用するかを理解したらそれを愛するために疑問が高まったことに気づきました。そのため、iOSまたはMacOSアプリを構築する際に遭遇する概念と一般的なユースケースを説明する多くのドキュメントを作成しようとしました。まだ質問や提案がある場合は、私がフィードバックに対して非常にオープンになっている場合、このREADMEの適切な名前のフィードバックセクションで貢献する方法について説明します。
Boutiqueは、わずか数行のコードを備えたリアルタイムのオフライン対応アプリを構築するのに非常に役立ちますが、上記のImagesControllerで実証したモデルビューコントローラーストアアーキテクチャを使用すると、さらに強力です。 MVCSは、あなたが知っているMVCアーキテクチャの親しみやすさとシンプルさをまとめて、 Storeの力で愛することで、アプリにシンプルだが明確に定義された国家管理とデータアーキテクチャを提供します。
それがどのように機能するかについて詳しく知りたい場合は、SwiftUIのMVCを探索するブログ投稿で哲学について読むことができます。また、このレポでBoutiqueを搭載したオフライン対応のリアルタイムMVCSアプリの参照実装を見つけることができます。
ブティックがここでできることの表面をひっかいただけです。 BodegaのStorageEngine活用すると、キャッシュデータからAPIサーバーとのインターフェースまで、すべてを実行する複雑なデータパイプラインを構築できます。 BoutiqueとBodegaはライブラリ以上のものであり、データ駆動型アプリケーションのプリミティブのセットであるため、ショットを提供し、デモアプリで遊んで、自分のアプリを構築することをお勧めします。
このプロジェクトは、メンテナーにフィードバックを提供する複数の形式を提供します。
ブティックについて質問がある場合は、最初にドキュメントを参照して、質問に答えられたかどうかを確認してください。
このプロジェクトは重く文書化されていますが、複数のサンプルプロジェクトも含まれています。
StorageEngineに取り組んでいる場合、このプロジェクトは、構築する必要があるオペレーションのパフォーマンスをテストする方法として役立ちます。まだ質問、強化、またはブティックを改善する方法がある場合、このプロジェクトはGitHubのディスカッション機能を活用しています。
バグを見つけて、問題を報告したい場合は感謝します。
Swift Package Managerは、Swiftコードの分布を自動化するためのツールであり、Swiftビルドシステムに統合されています。
Swiftパッケージをセットアップしたら、Boutiqueを依存関係として追加するのは、 Package.swiftの依存関係値に追加するのと同じくらい簡単です。
dependencies: [
. package ( url : " https://github.com/mergesort/Boutique.git " , . upToNextMajor ( from : " 1.0.0 " ) )
] SPMを使用したくない場合は、ファイルをコピーして、プロジェクトに手動でブティックを統合できます。
こんにちは、私はウェブ上のどこでもジョーですが、特にマストドンではジョーです。
ブティックの使用方法の詳細については、ライセンスをご覧ください。
ブティックは、開発者がより良いアプリを構築するのを支援するための愛の労働であり、自分の創造性を解き放ち、自分自身とユーザーにとって驚くべきものを作ることを容易にします。ブティックが貴重なものだと思うなら、私のオープンソースの仕事のスポンサーを支援することを検討してくれたら本当に感謝します。
あなたが何があなたのために用意されているかを知ったので、今は始める時が来ましたか?