Core Data vs GRDB vs Realm: как выбрать локальное хранилище для iOS
Сравнение Core Data, GRDB и Realm простыми словами: сложность запросов, планы синхронизации, отладка и понятный способ выбрать подходящий вариант.

Почему этот выбор быстро усложняется
Большинство приложений начинают с простого обещания: сохранить немного данных, прочитать их обратно и выпустить в продакшен. Потом приложение растет. Простой список превращается в поиск, фильтры, сортировку, фоновые обновления и работу с миграциями.
Именно поэтому выбор между Core Data, GRDB и Realm редко остается простым. Неподходящий вариант обычно не ломает первую версию. Он начинает мешать позже, когда обычная продуктовая работа замедляется и каждый новый экран требует большего от слоя хранения.
Потребности в запросах почти всегда растут после запуска. Приложение для заметок может начинаться с «показать все заметки», а вскоре ему понадобятся закрепленные элементы, теги, недавние изменения, поиск и способ отвечать на странные продуктовые вопросы без переписывания половины слоя данных. Если инструмент удобен для базового чтения, но неудобен для нестандартных запросов, эта боль будет повторяться снова и снова.
Планы на синхронизацию усложняют выбор еще сильнее. Многие команды говорят: «Нам нужно только локальное хранилище», и обычно они так и думают. Но через шесть месяцев появляются учетные записи, офлайн-правки, общие данные или backend, которому нужна обработка конфликтов. Когда в игру входит синхронизация, решение перестает быть только про скорость на одном устройстве. Оно начинает влиять на структуру данных, миграции и на то, сколько контроля у вас останется, когда записи не совпадут.
Удобство отладки важнее, чем многие признают. Когда баг появляется в продакшене, вам не важно, какой инструмент красиво выглядел в демо. Важно, можно ли посмотреть данные, залогировать запрос, воспроизвести проблему и доверять тому, что вы видите. Одним разработчикам спокойнее работать с обычным SQL и таблицами, которые можно увидеть напрямую. Другие предпочитают более высокий уровень абстракции. Проблемы начинаются, когда команда выбирает инструмент, который мешает привычному способу отладки.
Именно поэтому решение так быстро становится липким. Вы выбираете не просто место, где лежат байты на iPhone. Вы выбираете, насколько легко будет менять функции, отвечать на новые запросы и исправлять странные баги с данными под давлением. Слой хранения должен делать рутинную работу скучной. Если обычная работа уже кажется рискованной, он слишком дорогой.
Как выглядит ежедневная работа с каждым инструментом
Самая большая разница проявляется тогда, когда вы меняете модель во вторник, а в среду отлаживаете пропавшие данные. Все три варианта подходят для локального хранения на iOS, но ежедневная работа с ними ощущается очень по-разному, когда приложение вырастает за пределы простого прототипа.
Core Data хорошо вписывается в мир Apple. Xcode его понимает, файлы модели лежат внутри проекта, и многие iOS-разработчики уже знают базовые паттерны. Это помогает, когда вам нужен подход, близкий к стандартам Apple. Компромисс — скрытое поведение. Contexts, faults и правила слияния могут запутать даже опытную команду. Когда экран показывает устаревшие данные, вы можете потратить больше времени на отслеживание состояния приложения, чем на просмотр самих строк.
GRDB держит базу данных ближе к поверхности. Вы по-прежнему пишете на Swift, но SQLite остается видимым, и это часто облегчает жизнь. Можно напрямую посмотреть таблицы, прочитать схему и запустить тот же запрос вне приложения, если что-то выглядит странно. GRDB обычно ощущается простым и предсказуемым. Если команде нравится видеть настоящий SQL или хотя бы понимать, что происходит за API, GRDB обычно дает меньше сюрпризов.
Realm кажется более прямым, если команда думает объектами, а не таблицами. Вы создаете модели, сохраняете их и получаете обратно с меньшим количеством настройки. Для ранней разработки это может казаться очень быстрым, особенно в небольших приложениях. Компромисс в том, что Realm просит команду следовать его собственным правилам. Проверка данных менее универсальна, чем у SQLite, а некоторые шаги отладки завязаны на специфичные для Realm инструменты и привычки.
Изменения схемы еще сильнее показывают разницу. Core Data обычно неплохо справляется с простыми миграциями, но кастомные изменения могут быстро стать неудобными. GRDB делает миграции явными, поэтому вы точно знаете, что именно изменилось и когда. Realm держит миграции близко к объектной модели, и это кажется простым, пока не накапливаются старые версии приложения и странные крайние случаи.
Повседневный комфорт важнее списков возможностей. Если кому-то в команде нужно открыть файл данных, посмотреть сломанную запись и объяснить баг за десять минут, GRDB часто выигрывает. Если команда хочет максимально близкий к экосистеме Apple вариант, Core Data по-прежнему имеет смысл. Если нужен объектно-ориентированный подход и вы готовы к более навязанному инструменту, Realm может долго оставаться приятным.
Подбирайте инструмент под стиль запросов
Ваши экраны говорят о многом больше, чем списки функций. У приложения с несколькими списками и экранами деталей потребности в хранении сильно отличаются от приложения, которое фильтрует тысячи записей по статусу, дате, владельцу и сумме.
Если большинство чтений простые, подойдут все три варианта. Core Data и Realm часто ощущаются комфортно, когда приложение мыслит объектами: загрузить элементы, открыть одну запись, сохранить изменения, обновить экран. Если почти вся работа именно такая, нет смысла оптимизировать под отчетные запросы, которых у вас, возможно, никогда не будет.
Компромисс меняется, когда поиск становится сложнее. Фильтрованный поиск, многошаговая сортировка и пагинация звучат обычно, но они определяют ежедневную работу. GRDB выходит вперед, когда вам нужны явные запросы, которые можно прочитать, протестировать и настроить. Core Data по-прежнему может многое сделать через predicates и fetch requests, но сложную логику выборки иногда трудно проверить глазами. Realm удобен для многих стандартных фильтров, но дает меньше свободы, когда правила запроса становятся необычными.
Простое правило обычно работает хорошо:
- В основном списки, формы и экраны деталей: чаще подходят Core Data или Realm.
- Много фильтрации, ленты с сортировкой и пагинация: GRDB обычно проще для понимания.
- JOIN, агрегаты, дашборды или кастомный SQL: GRDB обычно подходит лучше.
Это особенно важно при больших локальных наборах данных. Допустим, вы храните на устройстве 200 000 товаров для полевого приложения продаж. На таком объеме быстрый ответ зависит от четких индексов, предсказуемых запросов и выборок, которые возвращают только то, что нужно экрану. GRDB дает прямой контроль над SQL и индексами, поэтому медленные запросы проще заметить. Core Data тоже может работать быстро, но нужна дисциплина с лимитами выборки, пакетной загрузкой и faults. Realm может казаться быстрым при просмотре записей, хотя индексированные поля все равно нужно выбирать внимательно.
В большинстве случаев стиль запросов решает спор быстрее, чем список возможностей. Если вы ожидаете JOIN, итоги, ранжированные результаты поиска или сложные правила фильтрации, выбирайте инструмент, который позволяет сразу увидеть запрос, когда что-то начинает тормозить.
Подумайте о синхронизации до того, как примете решение
Многим приложениям синхронизация в первый день не нужна. Трекер привычек, калькулятор, личный дневник или офлайн-инструмент для одного устройства могут оставаться локальными месяцами или годами. Если это ваш случай, не усложняйте и выбирайте хранилище, в котором проще всего читать, писать и отлаживать локальные данные.
Проблемы начинаются тогда, когда «может быть позже» на самом деле означает «скорее всего после запуска». Синхронизация меняет больше, чем просто хранение. Она меняет поток данных, правила конфликтов, обработку удалений и тесты, которые нужны для обычных действий пользователей.
Простой пример это показывает. Допустим, вы выпустили приложение для заметок, где все хранится на телефоне. Позже вы добавляете учетные записи и общие блокноты. Теперь одна заметка может измениться на двух устройствах до того, как одно из них снова подключится.
Правила нужны заранее. Побеждает самое новое изменение? Вы объединяете поля по отдельности? Сохраняете tombstones для удалений, чтобы удаленные элементы не возвращались? Эти решения влияют на IDs, timestamps, дизайн схемы и на то, как вы будете повторно запускать неудачные sync-задачи.
Вот где сравнение перестает быть вопросом вкуса. GRDB часто проще понять, если вы ожидаете собственный слой синхронизации, потому что таблицы SQLite и SQL-запросы делают поток данных видимым. Core Data тоже может хорошо работать, но некоторым командам труднее проверять собственную синхронизацию, когда появляются баги. Realm может выглядеть привлекательно, если синхронизация уже входит в план, но перед тем как строить вокруг него архитектуру, стоит проверить точный путь синхронизации, который вы будете использовать.
Несколько вопросов быстро проясняют ситуацию:
- Будут ли пользователи редактировать одну и ту же запись на нескольких устройствах?
- Должно ли приложение полностью работать офлайн часами или даже днями?
- Как вы будете отслеживать удаления и восстанавливаться после частичной синхронизации?
- Может ли команда отладить плохое слияние по логам и локальным данным?
Слабый план синхронизации может вынудить к болезненному переписыванию позже, даже если локальная база поначалу казалась вполне нормальной. Ошибки конфликтов трудно объяснить пользователям. Миграции усложняются, а тест-кейсы быстро множатся.
Если синхронизация хотя бы теоретически возможна, выбирайте хранилище, которое вы понимаете в условиях отказа, а не только в красивом демо.
Практичный способ выбрать
Большинство команд выбирают слишком рано. Они сравнивают функции и упускают то, что потом действительно мешает: форму собственного приложения.
Лучше проверить всё намеренно скучным способом. Запишите, что именно хранит приложение, как люди проходят через него и что коду нужно запрашивать в обычный день.
Начните с экранов, а не с базы данных. Составьте простой список экранов, которые читают локальные данные, и действий, которые их записывают. Список задач, результаты поиска, офлайн-редактирование, настройки, кэш профиля пользователя — когда вы видите чтения и записи рядом, многое проясняется.
Затем опишите три настоящих запроса, которые нужны приложению. Делайте их конкретными, например: «показать неоплаченные счета за последние 30 дней» или «найти заметки с тегом и отсортировать по последнему изменению». Если на бумаге эти запросы уже выглядят неудобно, в коде они будут еще хуже.
Добавьте один сценарий синхронизации, даже если она не входит в первую версию. Например, пользователь редактирует данные офлайн и подключается позже. Или два устройства меняют одну запись. Один такой сценарий часто влияет на ответ сильнее любого бенчмарка.
После этого сделайте небольшой spike. Один экран, один сценарий записи и те три запроса. Потом посмотрите, что именно сохраняется на диск, как выглядят миграции и какие ошибки возникают, если специально что-то сломать.
И наконец, выберите вариант, который команда сможет быстро отладить. Если запрос возвращает не те строки, может ли кто-то посмотреть сохраненные данные и объяснить баг за десять минут? Если нет, инструмент может быть рабочим, но стоимость все равно всплывет позже.
Небольшой spike обычно говорит больше, чем неделя обсуждений. Не нужен полноценный прототип. Часто достаточно одного честного уикенда тестирования.
Я бы доверял удобству отладки больше, чем списку функций. Инструмент, который выглядит элегантно в документации, но остается непрозрачным в продакшене, быстро становится дорогим. Если ваша команда легко читает SQL, GRDB часто ощущается очень прямолинейным. Если люди уже знают паттерны Apple, Core Data может подойти. Если нужен максимально объектный подход, Realm может казаться естественным, но только если история синхронизации и история отладки все еще подходят вашему приложению.
Простой пример приложения
Представьте трекер привычек. Каждый день пользователь отмечает выполненные привычки, добавляет теги вроде «здоровье» или «работа» и открывает недельный экран, где подсчитываются выполненные привычки, пропущенные дни и короткие серии подряд. Звучит просто, но уже здесь видно, как выбор локального хранилища для iOS влияет на реальную работу.
Если приложение только для Apple и следует привычным iOS-паттернам, Core Data — удобный выбор. Можно смоделировать Habit, Entry и Tag как связанные объекты, а потом использовать их в потоках SwiftUI или UIKit, которые многим iOS-разработчикам уже знакомы. Для трекера со стандартными экранами вроде «сегодня», «история» и «эта неделя» Core Data часто ощущается естественно.
GRDB начинает выглядеть лучше, когда недельная сводка становится требовательнее. Допустим, вам нужен запрос «все привычки с тегом health, выполненные не меньше четырех раз на этой неделе, отсортированные по самой длинной серии». В GRDB вы можете написать это через SQL и посмотреть точный запрос, когда цифры не совпадают. Это делает отчеты более надежными, особенно когда продуктовые требования постоянно меняются.
Realm особенно приятен, когда команда хочет объектные модели и быстрое локальное чтение. Загрузка сегодняшних записей, обновление привычки и наблюдение за изменениями могут ощущаться очень прямыми и легкими. Для небольшого приложения с простыми экранами такая скорость привлекает. Компромисс появляется тогда, когда отчеты превращаются в нестандартную работу с данными, а не в обычное чтение объектов.
Один и тот же трекер привычек может привести к трем разным решениям:
- Выбирайте Core Data, если приложение останется в мире Apple и команде нравятся встроенные iOS-паттерны.
- Выбирайте GRDB, если сводки, фильтры и отчеты будут становиться все более конкретными.
- Выбирайте Realm, если команда мыслит объектами в первую очередь и большинство чтений остается простым.
Трекер привычек — хороший тест, потому что сначала он кажется дружелюбным, а потом быстро усложняется. Пользователи начинают просить правила для streaks, фильтры по тегам, архивированные привычки, частичные выполнения и крайние случаи с датами. Если это примерное приложение уже кажется неудобным в одном инструменте, ваше реальное приложение, скорее всего, тоже будет таким.
Ошибки, которые потом создают проблемы
Самая дорогая ошибка — выбирать базу по популярности, а не по форме запросов. За Core Data стоит Apple, Realm часто кажется простым вначале, а GRDB выглядит знакомо, если команде нравится SQL. Но ни один из этих факторов не отвечает на главный вопрос: что именно ваше приложение будет просить базу делать каждый день?
Если ваши экраны зависят от фильтров, JOIN, сортировки, агрегатов и поиска, выбирайте исходя из этой реальности. Если приложение в основном хранит несколько пользовательских настроек, кэшированных элементов или простых записей, более легкий подход может подойти лучше. Неправильный выбор часто выглядит нормальным в первый релиз и начинает раздражать уже через полгода.
Миграции — еще одна медленная проблема. Команды часто ведут себя так, будто версия один навсегда станет моделью приложения. Потом приходят изменения продукта. Поле переименовывают, один объект превращается в три, старым записям нужны значения по умолчанию, и в каждом установочном экземпляре уже своя история. Если не подумать о миграциях заранее, в итоге приходится писать рискованные патчи под давлением сроков.
Синхронизация вызывает похожий хаос. Многие команды думают, что смогут прикрутить ее потом, но синхронизация меняет то, как вы проектируете IDs, timestamps, правила конфликтов, удаления и офлайн-правки. Приложение для заметок выглядит простым, пока одна и та же заметка не меняется на двух устройствах. Локальное хранилище и синхронизацию нужно обсуждать вместе, даже если синхронизация пока только в будущем.
Удобство отладки звучит скучно, пока в продакшене не появляется баг. Тогда вам нужно проверить сохраненные данные, сравнить ожидаемые и фактические значения и воспроизвести состояние на своем устройстве. Если на это уходит слишком много шагов, исправления замедляются. Простая проверка данных — это не приятный бонус. Она экономит часы.
Протестируйте неприятные случаи
Маленькие тестовые данные скрывают плохие решения. Прежде чем принять решение, проверьте всё на более грязном наборе:
- Импортируйте несколько тысяч записей.
- Запустите приложение после изменения схемы.
- Проверьте сортировку и фильтры на самых используемых экранах.
- Симулируйте два изменения одной и той же записи.
- Посмотрите на сохраненные данные после сбоя.
Слой хранения должен оставаться скучным под нагрузкой. Если он хорош только на счастливом пути, обычные продуктовые изменения сделают работу намного сложнее, чем нужно.
Короткий чек-лист перед выбором
Обычно команды жалеют об этом выборе только после того, как появляется реальная нагрузка. Небольшое демо может сделать любой из этих инструментов вполне нормальным, но ежедневная работа быстро показывает компромиссы.
Пять вопросов обычно хорошо отсекают шум:
- Как будут выглядеть ваши запросы через шесть месяцев? Если вы ожидаете JOIN, подсчеты, фильтры по нескольким таблицам или сгруппированные отчеты, GRDB обычно кажется более прямолинейным. Если чтения остаются простыми и похожими на объекты, Core Data или Realm могут быть удобнее.
- Как вы будете отлаживать плохую запись или отсутствующее поле? Некоторым командам важно открыть базу, посмотреть сырые строки и проверить состояние за минуты. GRDB делает это естественным, потому что SQLite остается видимым. Core Data и Realm могут быть менее прозрачными, когда баг появляется поздно вечером.
- Может ли синхронизация появиться скоро? Если продукт может добавить синхронизацию аккаунтов, офлайн-правки между устройствами или общее состояние, решите заранее, насколько тесную связь вы готовы принять. Слой хранения, который сегодня кажется удобным, позже может загнать вас в угол, если синхронизация изменит правила модели.
- Команде действительно комфортно работать с SQL и схемой? GRDB лучше всего работает там, где миграции, проверка запросов и проектирование таблиц никого не пугают. Если никто не хочет читать SQL, более объектный инструмент может сэкономить время, даже если он скрывает часть деталей.
- Сможете ли вы потом без хаоса перенести пользовательские данные? До окончательного выбора набросайте одну реальную миграцию: переименовать поле, разделить одну модель на две или добавить новую связь. Если уже на этом этапе кажется неудобно, в продакшене будет только сложнее.
Небольшое приложение для учета бюджета — хороший тест. Если оно хранит только недавние транзакции и простые категории, подойдут все три варианта. Если позже вы добавите ежемесячные сводки, общие бюджеты, обработку конфликтов и обращения в поддержку из-за пропавших сумм, ранний выбор начинает сильно влиять на проект.
Если два варианта все еще кажутся равными, выбирайте тот, который команда сможет проверить и исправить под давлением. Красивые абстракции быстро теряют очарование, когда одна сломанная миграция блокирует запуск приложения для всех пользователей.
Что делать дальше
Сделайте выбор конкретным. Напишите одностраничную заметку о решении, пока компромиссы еще свежи. Запишите реальные потребности приложения на бумаге, а не первое впечатление после чтения документации.
Сделайте это коротко и конкретно:
- какие данные приложение сохраняет чаще всего
- какие запросы пользователи запускают каждый день
- будет ли синхронизация только локальной, кастомной или вероятной позже
- какой вид боли в отладке команда готова терпеть, а какой нет
Потом проверьте выбор кодом. Сделайте один реальный экран, например список с поиском, созданием, редактированием и удалением, плюс одну миграцию для поля, которое вы точно измените. Такой маленький прототип быстро показывает правду. Разница между Core Data, GRDB и Realm становится намного яснее, когда вы переходите от файла модели к рабочему экрану и потом исправляете первый баг.
Измеряйте не только время настройки, но и время отладки. Отслеживайте, сколько уходит на просмотр сохраненных данных, поиск плохого запроса, понимание сообщения об ошибке и проверку, что миграция сработала. Инструмент, который кажется быстрым в первый день, может стать медленной тяготой, если команда начинает избегать работы с ним.
Простой пример делает это понятнее. Если сегодня вы строите трекер привычек, но позже можете добавить общие семейные аккаунты, планы по синхронизации важны уже сейчас, а не после запуска. Если приложение останется локальным, а модель данных простой, можно и выбор оставить простым.
Если это решение влияет на архитектуру приложения, будущую синхронизацию или более широкую продуктовую стратегию, второе мнение может стоить дешевле, чем переписывание. Oleg Sotnikov at oleg.is работает как fractional CTO и startup advisor, и такие ранние технические решения — как раз тот случай, когда короткий обзор очень помогает.
Часто задаваемые вопросы
Как выбрать между Core Data, GRDB и Realm?
Начните не с названия инструмента, а с того, как вы будете делать запросы. Если вам нужны свои фильтры, объединения, итоги или ранжированные результаты, чаще всего лучше подойдет GRDB. Если приложение живет по стандартным Apple-паттернам и в основном работает с объектами, подойдет Core Data. Если команде удобен объектный API и запросы довольно простые, Realm тоже может хорошо подойти.
Когда GRDB — лучший выбор?
GRDB обычно подходит лучше всего. SQLite остается на виду, поэтому вы можете посмотреть схему, настроить индексы, проверить строки и разобраться в медленных запросах без догадок. Это очень помогает, когда приложение уже работает не только со списками и экранами деталей.
Когда стоит выбрать Core Data?
Core Data имеет смысл, если вы делаете приложение только для Apple и команда уже знакома с его подходом к данным. Он хорошо работает для стандартных сценариев с связанными объектами, локальным хранением и кодом интерфейса, который опирается на стек Apple. Главное — чтобы команда уверенно чувствовала себя при отладке контекстов и состояния данных.
Когда есть смысл выбрать Realm?
Realm хорошо подходит, когда команда мыслит объектами, а не таблицами, и хочет быстро настроить локальное чтение и запись. Для небольших приложений с простыми экранами он часто ощущается очень быстрым. Я бы задумался, если вы ожидаете необычные запросы, сложную отчетность или собственный слой синхронизации в будущем.
Нужно ли думать о синхронизации до первой версии?
Да, если синхронизация с заметной вероятностью появится после запуска. Она меняет IDs, timestamps, обработку удалений, правила конфликтов и логику повторных попыток. Хранилище, которое удобно только на одном устройстве, может стать проблемой, когда два устройства меняют одну и ту же запись.
Что проще всего отлаживать в продакшене?
Обычно самый простой путь дает GRDB, потому что вы можете посмотреть таблицы напрямую и запустить тот же SQL вне приложения. Core Data и Realm тоже могут подойти, но они часто скрывают больше деталей, когда появляется плохая запись или устаревший экран. Выбирайте то, что команда сможет быстро проверить под давлением.
Насколько сложны миграции в каждом варианте?
Все три варианта умеют работать с миграциями, но делают это по-разному. GRDB оставляет миграции явными, поэтому вы видите каждое изменение схемы в коде. Core Data хорошо справляется со многими базовыми миграциями, но сложные изменения могут быстро стать неудобными. Realm держит миграционную логику близко к моделям, и это кажется простым, пока не накапливаются версии приложения.
Что если приложение хранит много данных на устройстве?
GRDB часто выигрывает, потому что большие объемы локальных данных лучше живут с понятным SQL, явными индексами и предсказуемыми запросами. Core Data тоже может работать быстро, если соблюдать дисциплину с лимитами выборки, пакетной загрузкой и подгрузкой объектов. Realm может ощущаться очень быстрым при просмотре записей, но поля для индексов все равно нужно проектировать внимательно.
Сначала надо замерять производительность или просто собрать небольшой тест?
Нет. Лучше сделать небольшой spike с одним реальным экраном, одним сценарием записи и несколькими настоящими запросами из вашего приложения. Потом намеренно сломайте миграцию, посмотрите на данные на диске и проверьте, как быстро команда находит проблему. Это расскажет больше, чем график бенчмарков.
Можно ли позже перейти с одного инструмента хранения на другой?
Можно, но перенос пользовательских данных почти всегда оказывается больнее, чем команды ожидают. Нужен аккуратный план миграции, тесты и время на крайние случаи из старых версий приложения. Сейчас лучше потратить выходные на проверку выбора, чем потом переписывать слой хранения после того, как пользователи уже на него завязались.