GitHub
SharingGRDB: быстрая, легкая замена SwiftData
Компания Point-Free выпустила значительное обновление библиотеки SharingGRDB, которая предлагает быструю, эргономичную и легкую замену SwiftData, работающую на базе SQL.
Компания Point-Free выпустила значительное обновление библиотеки SharingGRDB, которая предлагает быструю, эргономичную и легкую замену SwiftData, работающую на базе SQL. Она предоставляет API, аналогичные @Model
, @Query
и #Predicate
, но настроена на прямой доступ к базовой базе данных (то, от чего абстрагируется SwiftData), что дает вам больше возможностей, больше гибкости и больше производительности при сохранении и получении данных в приложении.
Макрос @Table
Основным новшеством, используемым библиотекой, является новый макрос @Table
, который открывает богатый, безопасный для типов язык построения запросов, а также высокопроизводительный декодер для преобразования примитивов базы данных в первоклассные типы данных Swift. Он служит целям, аналогичным (и по синтаксису) макросу @Model
в SwiftData:
SharingGRDB
@Table struct Reminder { let id: Int var title = "" var isCompleted = false }
SwiftData
@Model class Reminder { var title: String var isCompleted: Bool init( title: String = "", isCompleted: Bool = false ) { self.title = title self.isCompleted = isCompleted } }
Некоторые ключевые различия:
- Макрос
@Table
работает со структурами, в то время как@Model
работает только с классами. - Поскольку
@Model
-версияReminder
является классом, необходимо предоставить инициализатор. - Версии
Reminder
с@Model
не нужно полеid
, поскольку SwiftData предоставляет постоянный идентификатор (persistentIdentifier) каждой модели.
Применив @Table
, Reminder
получает мгновенный доступ к мощному набору API для построения запросов, который позволяет создавать различные запросы с помощью выразительного Swift, подобно тому, как SwiftData использует #Predicate
и ключевые пути в макросе @Query
:
SharingGRDB
@FetchAll( Reminder.where { $0.title.contains("get") && !$0.isCompleted } .order(by: \.title) ) var reminders
SwiftData
@Query( filter: #Predicate<Reminder> { $0.title.contains("get") && !$0.isCompleted }, sort: \Reminder.title ) var reminders: [Reminder]
Оба вышеприведенных примера получают элементы из внешнего хранилища данных, используя типы данных Swift, и оба автоматически наблюдаются SwiftUI, чтобы представления пересчитывались при изменении внешних данных, но SharingGRDB можно использовать и за пределами представления: в моделях @Observable
, контроллерах представления UIKit и других.
Конструктор запросов отображает синтаксически правильный SQL, поэтому вы можете быть уверены, что он будет работать еще во время компиляции. Между тем, #Predicate
вполне можно использовать таким образом, что в лучшем случае это приведет к загадочным ошибкам при компиляции, а в худшем — к краху во время выполнения.
Например, использование в запросе вычисляемого, а не хранимого свойства — это ошибка компилятора в SharingGRDB, но сбой во время выполнения в SwiftData:
SharingGRDB
@FetchAll( Reminder.where { // 'Reminder.TableColumns' has // no member 'isNotCompleted' $0.isNotCompleted } .order(by: \.title) ) var reminders
SwiftData
@Query( filter: #Predicate<Reminder> { // Fatal error: Couldn't find // 'isNotCompleted' on Reminder $0.isNotCompleted }, sort: \Reminder.title ) var reminders: [Reminder]
Конструктор запросов также открывает перед вами весь спектр возможностей SQL, в то время как SwiftData скрывает эти детали и вместо этого предоставляет свой собственный язык построения запросов, который может выполнять только часть задач, которые может выполнять SQL.
Все, что можно сделать с помощью SwiftData, и даже больше, можно сделать с помощью SharingGRDB. Подробнее см. в разделе «Сравнение со SwiftData«.
Безопасные строки SQL
Мы никогда не хотели, чтобы наш конструктор запросов мешал написанию конкретного запроса. Поэтому мы создали макрос #sql
, который позволяет отказаться от синтаксиса построителя запросов и писать SQL непосредственно в виде строки, но при этом безопасным способом.
В качестве простого примера можно выбрать заголовки из всех напоминаний следующим образом:
@FetchAll( #sql("SELECT title FROM reminders", as: String.self) ) var reminderTitles
Также можно сохранить безопасность схемы при написании SQL в виде строки. Вы можете использовать интерполяцию строк вместе со статическим описанием вашей схемы, предоставляемым @Table
, чтобы ссылаться на ее столбцы и имя таблицы:
@FetchAll( #sql("SELECT \(Reminder.title) FROM \(Reminder.self)", as: String.self) ) var reminderTitles
Это генерирует тот же запрос, что и раньше, но теперь у вас есть больше статической безопасности при обращении к именам столбцов и таблиц в ваших типах.
Вы даже можете выбрать все столбцы из таблицы напоминаний, используя статическое свойство columns
:
@FetchAll( #sql("SELECT \(Reminder.columns) FROM \(Reminder.self)", as: Reminder.self) ) var reminders
Обратите внимание, что это позволяет теперь декодировать результат в полный тип Reminder
.
Макрос #sql
также можно использовать для введения строк SQL в конструктор запросов с выбранной вами степенью детализации:
let searchTerm = "order%" @FetchAll( Reminder .where { #sql("\($0.title) COLLATE NOCASE NOT LIKE \(bind: searchTerm)") } .order(by: \.title) ) var reminders
Но это лишь поверхностный обзор. Макрос #sql
также выполняет базовую проверку строк SQL, чтобы выявить синтаксические ошибки во время компиляции. Дополнительные сведения см. в разделе «Безопасные SQL-строки«.
Производительность
SharingGRDB использует высокопроизводительное декодирование для преобразования полученных данных в ваши доменные типы Swift и имеет профиль производительности, аналогичный прямому вызову SQLite C API.
Ниже приведены бенчмарки с набором тестов производительности Lighter, чтобы увидеть, как это сравнивается:
Orders.fetchAll setup rampup duration SQLite (generated by Enlighter 1.4.10) 0 0.144 7.183 Lighter (1.4.10) 0 0.164 8.059 SharingGRDB (0.2.0) 0 0.172 8.511 GRDB (7.4.1, manual decoding) 0 0.376 18.819 SQLite.swift (0.15.3, manual decoding) 0 0.564 27.994 SQLite.swift (0.15.3, Codable) 0 0.863 43.261 GRDB (7.4.1, Codable) 0.002 1.07 53.326
Стало возможным благодаря StructuredQueries
Причина, по которой нам удалось добиться значительных успехов в эргономике и производительности SharingGRDB, кроется в еще одной библиотеке, которую мы выпускаем сегодня: StructuredQueries. Она предоставляет набор инструментов, позволяющих писать безопасный, выразительный, композабл SQL с помощью Swift, включая макрос @Table
и его API для построения запросов, макрос #sql
и многое другое.
Вы просто прикрепляете макросы к типам, которые представляют схему вашей базы данных. Развивая предыдущий пример:
@Table struct Reminder { let id: Int var title = "" var isCompleted = false var priority: Priority? @Column(as: Date.ISO8601Representation?.self) var dueDate: Date? }
Он предоставляет широкий набор API для создания запросов, начиная от простых:
Swift
Reminder.all // => [Reminder]
SQL
SELECT "reminders"."id", "reminders"."title", "reminders"."isCompleted", "reminders"."priority", "reminders"."dueDate" FROM "reminders"
И до сложных:
Swift
Reminder .select { ($0.priority, $0.title.groupConcat()) } .where { !$0.isCompleted } .group(by: \.priority) .order { $0.priority.desc() } // => [(Priority?, String)]
SQL
SELECT "reminders"."priority", group_concat("reminders"."title") FROM "reminders" WHERE (NOT "reminders"."isCompleted") GROUP BY "reminders"."priority" ORDER BY "reminders"."priority" DESC
Эти API помогают вам избежать проблем, связанных с опечатками и ошибками типов, но при этом они по-прежнему воспринимают SQL таким, какой он есть. StructuredQueries — это не ORM и не новый язык запросов, который вам придется изучать: ее API разработаны так, чтобы читать SQL, который она генерирует, хотя он часто более лаконичный и всегда более безопасный.
Библиотека поддерживает построение всего: от операторов SELECT, INSERT, UPDATE и DELETE до безопасных для типов внешних объединений и рекурсивных выражений общей таблицы. Чтобы узнать больше о построении SQL с помощью StructuredQueries, ознакомьтесь с документацией.
И хотя релиз StructuredQueries ориентирован на SQLite и, в частности, на его драйвер SharingGRDB, библиотека является универсальной, и ее построитель и декодер запросов может взаимодействовать с другими базами данных (MySQL, Postgres и т.д.) и библиотеками баз данных.
Если вы заинтересованы в создании интеграции StructuredQueries с другой библиотекой баз данных, пожалуйста, начните обсуждение и сообщите нам о любых проблемах, с которыми вы столкнетесь.
Попробуйте уже сегодня
Релиз 0.2.0 SharingGRDB вышел уже сегодня! Попробуйте и дайте нам знать, что вы думаете. Если у вас есть вопросы или комментарии, присоединяйтесь к нашим обсуждениям.
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.16
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.17
-
Разработка4 недели назад
Расширенные архитектурные правила в SwiftLint: часть 1
-
Видео и подкасты для разработчиков4 недели назад
Не два байта переслать: эмуляция бесконтактных карт на мобильных устройствах