Приходилось ли писать кастомные миграции для Core Data?

Ответ

Да, приходилось. Кастомные (тяжелые) миграции в Core Data необходимы, когда автоматическая лёгкая миграция (lightweight migration) невозможна. Это происходит при сложных изменениях схемы данных, таких как:

  • Разделение одного атрибута на несколько.
  • Объединение нескольких атрибутов в один.
  • Нелинейные преобразования данных (например, изменение формата даты или вычисление нового значения на основе старых).
  • Изменение иерархии сущностей (entity inheritance).

Процесс и пример: Миграция выполняется с помощью NSEntityMigrationPolicy. Необходимо создать Mapping Model (.xcmappingmodel) и указать в нём кастомный класс политики.

Пример политики миграции для преобразования полного имени (fullName) в отдельные атрибуты firstName и lastName:

import CoreData

class SplitNameMigrationPolicy: NSEntityMigrationPolicy {

    @objc func transformFullName(forFullName: NSString?) -> [String: String]? {
        // Логика разделения строки
        guard let fullName = forFullName as String? else { return nil }
        let components = fullName.split(separator: " ").map(String.init)

        let firstName = components.first ?? ""
        let lastName = components.dropFirst().joined(separator: " ") // На случай двойных фамилий

        return ["firstName": firstName, "lastName": lastName]
    }
}

В Mapping Model для атрибута firstName в качестве Value Expression указывается: FUNCTION($entityPolicy, "transformFullNameForFullName:", $source.fullName)["firstName"].

Критически важные практики:

  1. Тестирование на копии реальной базы данных перед выпуском обновления приложения.
  2. Поэтапные миграции для очень больших изменений (версия 1 -> 2 -> 3).
  3. Обеспечение обратной совместимости и обработка nil-значений.
  4. Информирование пользователя о процессе, так как миграция может занять время.

Ответ 18+ 🔞

Ох, блядь, вспомнил — да, приходилось, сука, вляпываться в это болото. Кастомные, они же тяжёлые, миграции в Core Data — это когда автоматическая лёгкая миграция (lightweight migration) просто пиздец как не катит. Такое случается, когда у тебя в схеме данных происходят хуёво-сложные изменения, типа:

  • Один атрибут надо распидорасить на несколько.
  • Несколько атрибутов склеить в один, как говно с лопатой.
  • Данные нужно преобразовать по какой-то ёбаной нелинейной логике — скажем, поменять формат даты или высрать новое значение из старых.
  • Ковыряешься в иерархии сущностей (entity inheritance) — тут вообще пиздец начинается.

Как это, блядь, делается и пример: Всё вертится вокруг NSEntityMigrationPolicy. Нужно создать Mapping Model (этот файлик .xcmappingmodel) и в нём указать свой кастомный класс.

Вот, например, политика, которая из одного поля fullName делает два отдельных — firstName и lastName:

import CoreData

class SplitNameMigrationPolicy: NSEntityMigrationPolicy {

    @objc func transformFullName(forFullName: NSString?) -> [String: String]? {
        // Логика разделения строки
        guard let fullName = forFullName as String? else { return nil }
        let components = fullName.split(separator: " ").map(String.init)

        let firstName = components.first ?? ""
        let lastName = components.dropFirst().joined(separator: " ") // На случай двойных фамилий

        return ["firstName": firstName, "lastName": lastName]
    }
}

А потом в этом Mapping Model'е для атрибута firstName в поле Value Expression пишешь какую-то хуйню вида: FUNCTION($entityPolicy, "transformFullNameForFullName:", $source.fullName)["firstName"]. В рот меня чих-пых, выглядит страшно, но работает.

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

  1. Тестируй на копии реальной базы, ёпта! Не вздумай выпускать обновление, не проверив на живых данных — будет тебе овердохуища боли.
  2. Если изменения пиздец какие огромные, делай поэтапные миграции (версия 1 -> 2 -> 3). Не пытайся прыгнуть с первого этажа на десятый.
  3. Обратная совместимость и обработка nil — святое. Твой код не должен разъебаться, если в старых данных какая-то хуйня.
  4. Предупреди пользователя! Эта штука может ебашить долго. Пусть знает, что приложение не зависло, а просто занимается важным делом — перекладыванием говна из одной кучи в другую.