Какие преимущества и недостатки у базы данных Realm по сравнению с Core Data и SQLite?

Ответ

Преимущества:

  • Высокая производительность: Использует собственный движок хранения, оптимизированный для объектов. Часто работает быстрее, чем Core Data с SQLite в качестве бэкенда, особенно на операциях чтения и фильтрации.
  • Реактивные обновления: Подписки (observe) и Results автоматически обновляются при изменении данных, что идеально подходит для связки с UI (например, с UITableView).
  • Прямой доступ к объектам: Не требуется контекст или фетч-запросы для получения объектов. Модели наследуются от Object и сразу готовы к работе.
  • Простая миграция для схемы: Для добавления/удаления полей часто достаточно просто изменить класс модели. Удаление полей и сложные преобразования требуют ручной логики в блоке миграции.
  • Кроссплатформенность: Одна и та же база данных (файл .realm) может использоваться на iOS, Android и других платформах.

Недостатки и ограничения:

  • Размер файла базы данных: Может быстро расти, так Realm резервирует пространство. Требуется периодическое сжатие (Realm.Configuration.shouldCompactOnLaunch или writeCopy).
  • Модель данных наследует от Object: Это накладывает ограничения на дизайн моделей (например, нельзя наследовать от других пользовательских классов).
  • Потоковая безопасность: Объекты Realm привязаны к потоку, на котором были созданы. Для передачи между потоками необходимо использовать ThreadSafeReference или заново запрашивать объект по первичному ключу в другом потоке.
  • Отсутствие каскадного удаления "из коробки": При удалении родительского объекта связанные дочерние объекты не удаляются автоматически, это нужно делать вручную.
  • Проприетарная лицензия: Для некоторых сценариев использования требуется коммерческая лицензия.

Примеры использования и миграции:

// 1. Определение модели
import RealmSwift

class Dog: Object {
    @Persisted(primaryKey: true) var id: ObjectId
    @Persisted var name: String
    @Persisted var age: Int
    @Persisted var owner: Person? // To-one связь
    @Persisted var toys: List<Toy> // To-many связь
}

// 2. Конфигурация с шифрованием и миграцией
let config = Realm.Configuration(
    schemaVersion: 2,
    encryptionKey: getEncryptionKey(),
    migrationBlock: { migration, oldSchemaVersion in
        if oldSchemaVersion < 2 {
            // Миграция с версии 1 на 2: добавляем новое поле 'breed'
            migration.enumerateObjects(ofType: Dog.className()) { oldObject, newObject in
                newObject!["breed"] = "Unknown"
            }
        }
    }
)

let realm = try! Realm(configuration: config)

// 3. Реактивные наблюдения
let puppies = realm.objects(Dog.self).filter("age < 2")
let token = puppies.observe { changes in
    switch changes {
    case .initial(let results):
        print("Initial load: (results.count) puppies")
    case .update(let results, let deletions, let insertions, let modifications):
        // Автоматически обновить UITableView/UICollectionView
        print("Changes: +\(insertions.count), -\(deletions.count), ~\(modifications.count)")
    case .error(let error):
        print("Error: (error)")
    }
}

// 4. Запись в транзакции
try! realm.write {
    let newDog = Dog()
    newDog.name = "Rex"
    newDog.age = 1
    realm.add(newDog)
}

// Не забудьте инвалидировать токен наблюдения
// token.invalidate()

Ответ 18+ 🔞

Ага, слушай, вот этот ваш Realm, ну, такая штука, блядь. Как будто взяли Core Data, да по ебалу ему настучали, чтобы не выёбывался со своими контекстами и фетчами. В общем, расскажу по-простому, что тут к чему.

Что в нём, блядь, хорошего, а?

  • Быстрый, как хуй с горы. У них там свой движок, под объекты заточенный. Читает и фильтрует так, что Core Data с её SQLite-бэкендом иногда курит в сторонке, особенно когда данные просто взять надо.
  • Сам всё обновляет, хитрая жопа. Подписки (observe) и результаты (Results) — они живые. Изменились данные в базе — тут же в интерфейсе всё дернулось. Для UITableView это вообще ёперный театр, красота.
  • Объекты прямо в руки. Не надо через хуй с контекстами прыгать. Наследовал модель от Object — и всё, работай с ней как есть. Никаких фетч-реквестов, ебать мои старые костыли.
  • Миграции часто простые. Добавил поле в класс — и в рот меня чих-пых, в базе оно появилось. Правда, если поле удаляешь или сложное преобразование нужно — тут уже руками в блоке миграции повозиться придётся.
  • Кроссплатформенный. Файлик .realm и на iOS, и на Android пойдёт. Один и тот же, блядь.

А теперь, сука, подводные камни:

  • Файл базы жиреет, как на дрожжах. Realm любит место про запас резервировать. Так что периодически его надо сжимать, иначе овердохуища гигабайтов наберет. Есть shouldCompactOnLaunch или writeCopy для этого.
  • Модели — рабы Object. От этого класса наследоваться надо, а от своих собственных — низя. Дизайн иногда, блядь, кривоватый получается.
  • Потоковая безопасность — отдельная песня. Объект, созданный в одном потоке, в другом — просто кусок говна, непригодный. Тащить между потоками надо через ThreadSafeReference или по первичному ключу заново вытаскивать. Забудешь — пиздец, креш.
  • Каскадного удаления нет, мудя. Удалил родителя — дети так и останутся сиротами в базе. Надо самому, вручную, в транзакции подчищать, идиотская работа.
  • Лицензия у них, блядь, проприетарная. Для коммерческих проектов смотри, не напорешься. Хуй с винтом потом выкручивать.

Ну и примерчики, чтобы понятно было, как этим еблом пользоваться:

// 1. Модель объявляем. Проще пареной репы.
import RealmSwift

class Dog: Object {
    @Persisted(primaryKey: true) var id: ObjectId
    @Persisted var name: String
    @Persisted var age: Int
    @Persisted var owner: Person? // Связь один-к-одному
    @Persisted var toys: List<Toy> // Связь один-ко-многим
}

// 2. Настраиваем, с шифрованием и миграцией. Главное — версию схемы не проебать.
let config = Realm.Configuration(
    schemaVersion: 2,
    encryptionKey: getEncryptionKey(),
    migrationBlock: { migration, oldSchemaVersion in
        if oldSchemaVersion < 2 {
            // Мигрируем с 1 на 2: добавляем поле 'breed'
            migration.enumerateObjects(ofType: Dog.className()) { oldObject, newObject in
                newObject!["breed"] = "Unknown"
            }
        }
    }
)

let realm = try! Realm(configuration: config)

// 3. Реактивные наблюдения. Магия, блядь.
let puppies = realm.objects(Dog.self).filter("age < 2")
let token = puppies.observe { changes in
    switch changes {
    case .initial(let results):
        print("Загрузили сразу: (results.count) щенков")
    case .update(let results, let deletions, let insertions, let modifications):
        // Идеально для апдейта таблицы. Сам всё посчитал, сука.
        print("Изменения: +(insertions.count), -(deletions.count), ~(modifications.count)")
    case .error(let error):
        print("Ошибка, блядь: (error)")
    }
}

// 4. Запись. ТОЛЬКО в транзакции! Иначе — исключение, иди нахуй.
try! realm.write {
    let newDog = Dog()
    newDog.name = "Rex"
    newDog.age = 1
    realm.add(newDog)
}

// И НЕ ЗАБЫВАЙ токен наблюдения убить, а то память потечёт!
// token.invalidate()

Вот, в общем-то, и всё. Инструмент мощный, но со своими тараканами. Если в лоб по учебнику использовать — норм. Начинаешь выёбываться со сложными связями между потоками — готовься, блядь, волосы на жопе рвать.