
«Я разорвал основные данные, так и должно работать»
- Джош Хольц
«Бутик смехотворно легко реализовать, и делает настойчивость ветеркой. Это стало моим первым дополнением к каждому проекту, который я начинаю.
- Тайлер Хиллсман
«Бутик стал бесценным, я сейчас использую его в каждой стороне. Не нужно заботиться о настойчивости - это здорово, а стоимость начала работы практически равна нулю».
- Роман Пукет
Если вы найдете бутик ценным, я бы очень признателен, если бы вы подумали о том, чтобы помочь спонсировать мою работу с открытым исходным кодом, чтобы я мог продолжать работать над такими проектами, как бутик, чтобы помочь разработчикам, как вы.
Boutique-это простая, но мощная библиотека настойчивости, небольшой набор оберток и типов недвижимости, которые позволяют создавать невероятно простые приложения, управляемые состоянием для Swiftui, Uikit и Appkit. Благодаря двойной архитектурной архитектурной архитектуре в память + дисковом кэшировании бутик предоставляет способ создать приложения, которые обновляются в режиме реального времени с полным автономным хранилищем только в нескольких строках кода, используя невероятно простой API. Бутик построен на Bodega, и вы можете найти демонстрационное приложение, созданное на архитектуре хранилища модели контроллера в этом репо, которое показывает вам, как сделать готовое приложение Swiftui в автономном режиме только в нескольких строках кода. Вы можете прочитать больше о мышлении, стоящей за архитектурой в этом сообщении, изучающем архитектуру MVCS.
У бутика есть только одна концепция, которую вам нужно понять. Когда вы сохраняете данные в Store ваши данные будут автоматически сохраняться для вас и будут обнаружены как обычный быстрый массив. @StoredValue и @SecurelyStoredValue Обертки свойств работают так же, но вместо массива они работают с единственными ценностями. Вам никогда не придется думать о базах данных, все в вашем приложении - это обычный быстрый массив или ценность, используя модели вашего приложения, с простым кодом, который выглядит как любое другое приложение.
Вы можете быть знакомы с Store от Redux или композиционной архитектуры, но в отличие от этих рамок вам не нужно беспокоиться о добавлении действий или редукторов. С помощью этой реализации 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's Horeshine.
² Под капотом в магазине выполняет работу по сохранению всех изменений на диск при добавлении или удалении предметов.
«В Swiftui вы можете даже включить свое View с помощью $items и использовать .onReceive() для обновления и манипулирования данными, опубликованными $items магазина.
Предупреждение о хранении изображений или других двоичных данных в бутике технически поддерживается, но не рекомендуется. Причина в том, что хранение изображений в бутике может взять с собой в магазин в памяти и память вашего приложения в результате. По тем же причинам, как и не рекомендуется хранить изображения или бинарные капли в базе данных, не рекомендуется хранить изображения или бинарные капли в бутике.
Мы рассмотрим обзор высокого уровня обертки свойства @Stored Property, но @Stored полностью задокументирована с помощью контекста, вариантов использования и примеров здесь.
Это было легко, но я хочу показать вам что -то, что заставляет бутик чувствовать себя совершенно волшебным. Store -это простой способ получить преимущества офлайн-хранения и обновлений в реальном времени, но, используя обертку @Stored Property, мы можем кэшировать любое свойство в памяти и на диске только одной строкой кода.
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 Frapers ниже, но они полностью задокументированы контекстом, вариантами использования и примерами здесь.
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 Properfore может делать все, что делает @StoredValue , но вместо хранения значений в UserDefaults @SecurelyStoredValue будет сохраняться элементы в ключевой мачине системы. Это идеально подходит для хранения конфиденциальных значений, таких как пароли или токены авто, которые вы не захотите хранить в UserDefaults .
Если у вас есть какие -либо вопросы, я бы сначала попросил вас, пожалуйста, посмотрите на документацию, и бутик, и бодега очень задокументированы. Помимо этого бутика поставляется не с одним, а двумя демонстрационными приложениями, каждое из которых служит другой цели, но демонстрируя, как вы можете создать приложение, поддерживаемое бутиком.
Когда я строил v1, я заметил, что людям, которые получили бутик, любили это, и людям, которые думали, что это может быть хорошо, но были вопросы, чтобы полюбить его, как только они поняли, как его использовать. Из -за этого я стремился написать много документации, объясняющих концепции и общие случаи использования, с которыми вы столкнетесь при создании приложения iOS или MacOS. Если у вас все еще есть вопросы или предложения, я очень открыт для отзывов, как внести свой вклад, обсуждается в точно названном разделе обратной связи этого Readme.
Boutique очень полезен сам по себе для создания приложений в режиме реального времени, готовых к автономным режимам, с несколькими строками кода, но он еще более мощный, когда вы используете архитектуру магазина модели контроллера, которую я разработал, продемонстрировавшаяся в ImagesController . Контроллер выше. MVCS объединяет знакомство и простоту архитектуры MVC, которую вы знаете, и любите силу Store , чтобы дать вашему приложению простую, но четко определенное управление состоянием и архитектуру данных.
Если вы хотите узнать больше о том, как это работает, вы можете прочитать о философии в сообщении в блоге, где я изучаю MVCS для Swiftui, и вы можете найти справочную реализацию приложения MVCS, готового в реальном времени, основанного на бутике в этом репо.
Мы только поцарапали поверхность того, что бутик может сделать здесь. Используя StorageEngine Bodega, вы можете создавать сложные трубопроводы, которые делают все, от кэширования данных до взаимодействия с вашим сервером API. Boutique и Bodega-это больше, чем библиотеки, это набор примитивов для любого приложения, управляемого данными, поэтому я предлагаю дать им шанс, играть с демонстрационным приложением и даже создать собственное приложение!
Этот проект предоставляет несколько форм предоставления обратной связи для сопровождающих.
Если у вас есть вопрос о бутике, мы просим вас сначала проконсультироваться с документацией, чтобы увидеть, был ли на вашем вопросе.
Этот проект сильно задокументирован, но также включает в себя несколько образцов проектов.
StorageEngine этот проект будет послужить вам хорошо, чтобы проверить производительность операций, которые вам необходимы.Если у вас все еще есть вопрос, улучшение или способ улучшить бутик, этот проект использует функцию дискуссий GitHub.
Если вы найдете ошибку и хотите сообщить о проблеме, будет оценена.
Swift Package Manager - это инструмент для автоматизации распространения Swift Code и интегрирован в систему сборки Swift.
После того, как у вас настройка пакета Swift добавить бутик в качестве зависимости так же просто, как добавить его к значению зависимостей вашего Package.swift .
dependencies: [
. package ( url : " https://github.com/mergesort/Boutique.git " , . upToNextMajor ( from : " 1.0.0 " ) )
] Если вы предпочитаете не использовать SPM, вы можете интегрировать бутик в свой проект вручную, копируя файлы.
Привет, я Джо везде в Интернете, но особенно на Мастодоне.
Смотрите лицензию для получения дополнительной информации о том, как вы можете использовать бутик.
Boutique - это труд любви, чтобы помочь разработчикам создать лучшие приложения, облегчая вам разблокировать свое творчество и сделать что -то удивительное для своих и ваших пользователей. Если вы найдете бутик ценным, я бы очень признателен, если бы вы подумали о том, чтобы помочь спонсировать мою работу с открытым исходным кодом, поэтому я могу продолжать работать над такими проектами, как бутик, чтобы помочь разработчикам, как вы.
Теперь, когда вы знаете, что вам ждет , пора начать ?