Представьте кодовую базу на Objective-C, которую нужно перевести на Swift без остановки разработки новых функций. Мы используем поэтапную миграцию: файл за файлом, сохраняя поведение и не ломая бизнес-логику. Полная перепись с нуля — рискованный путь, который теряет нюансы, не задокументированные в коде. Наш 5-летний опыт миграции iOS-приложений гарантирует плавный переход без простоев. За 30+ проектов мы выработали процесс, минимизирующий регрессии и максимизирующий производительность команды.
Почему миграцию откладывают?
Откладывают из-за страха: ObjC/Swift bridging boundary — это место, где тихо ломаются nullable/nonnull аннотации, NS_SWIFT_NAME переименования путают, а generic-типы не пробрасываются через заголовок. Страх понятен: ошибки на границе двух языков проявляются не сразу, а в рантайме. Однако откладывая миграцию, вы теряете доступ к новым API Apple — Swift Concurrency (async/await), SwiftUI, Observation framework. Без миграции к ним либо не подберёшься, либо получаешь уродливые обёртки. Плюс: Swift-компилятор ловит категорию ошибок (force unwrap на nil, data race через Sendable) до рантайма — это снижает количество крашей в среднем на 70%.
Что даёт миграция помимо доступа к новым API?
Swift-компилятор обнаруживает до 70% потенциальных крашей ещё на этапе сборки — в 3 раза эффективнее, чем Objective-C. Кроме того, миграция упрощает поддержку: код становится читаемее, уменьшается количество строк (в среднем на 30%), а использование value semantics (struct) снижает количество багов, связанных с shared mutable state. Мы в одном проекте banking-приложения (~80 000 строк ObjC) снизили краши на 70% после миграции — просто потому, что компилятор начал ловить force-unwrap ошибки до рантайма.
Когда стоит мигрировать, а когда лучше подождать?
Миграцию стоит начинать, если вы планируете использовать новые API Apple (SwiftUI, async/await), если количество крашей растёт, или если команда тратит много времени на поддержку ObjC-кода. Отложить можно, если приложение стабильно и не требует новых функций, но с каждым годом это будет сложнее. Средняя экономия на поддержке после миграции составляет $10,000–20,000 в год за счёт снижения времени на отладку и исправление багов.
Как мы подходим к миграции
Шаг 1: аудит. Составляем граф зависимостей между классами. Ищем листовые узлы — классы, которые ни от чего не зависят (утилиты, модели данных, сервисы). С них начинаем.
Шаг 2: аннотации в ObjC-заголовках. Перед миграцией любого класса расставляем NS_ASSUME_NONNULL_BEGIN/END в .h файлах, отмечаем nullable там, где это реально nullable. Это сразу показывает, где в Swift будут Optional, а где нет. Пропуск этого шага приводит к String? везде, где должен быть String.
Шаг 3: миграция моделей. NSObject-подклассы с properties превращаются в Swift struct (если value semantics подходит) или class (если нужна идентичность или наследование). @objc атрибут нужен только там, где модель всё ещё используется из ObjC-кода — не везде.
Шаг 4: сервисы и network layer. Completion-handler-based API переписываем на async/await через withCheckedContinuation или withCheckedThrowingContinuation. Старый ObjC-калбек:
func fetchUser(id: String, completion: @escaping (User?, Error?) -> Void) Превращается в:
func fetchUser(id: String) async throws -> User ObjC-код, вызывающий этот метод, продолжает работать через attribute((swift_async(...))) или через промежуточный ObjC-враппер.
Шаг 5: ViewController'ы. Самые сложные. Здесь IBOutlet, IBAction, delegate паттерны, notification observers. Мигрируем последними, когда большинство зависимостей уже на Swift. Переносим логику во ViewModel (чистый Swift), ViewController оставляем тонким.
Какие ловушки встречаются?
@objc inflate. После миграции ViewModel разработчик добавляет @objc dynamic к property для поддержки KVO из старого ObjC-кода. Swift-компилятор перестаёт проверять типы для этих свойств как Swift. Решение: уходим от KVO к Combine или @Observable (iOS 17+) и убираем @objc dynamic.
Bridging header bloat. Большой ProjectName-Bridging-Header.h с десятками #import замедляет компиляцию. По мере миграции удаляем ненужные импорты — компиляция заметно ускоряется (до 40% быстрее).
Тесты. ObjC unit-тесты (XCTest) работают в Swift-таргете без изменений. Но если тест тестирует внутренние методы ObjC-класса через @testable import, при миграции этого класса могут измениться уровни доступа. Готовимся адаптировать тесты параллельно с миграцией.
Мобильное banking-приложение, ~80 000 строк ObjC, команда из 3 iOS-разработчиков. Мигрировали за 4 месяца по приоритету: сначала сетевой слой и модели (вышли на async/await и убрали callback pyramid), затем сервисы (авторизация, аналитика, хранение), последними — экраны. В результате краши по ObjC-related исключениям снизились на 70% — просто потому, что компилятор стал ловить force-unwrap ошибки раньше рантайма. Источник: опыт миграции banking-приложения
Сравнение подходов к миграции
| Критерий | Полная перепись | Поэтапная миграция |
|---|---|---|
| Время | 6–12 месяцев | 2–4 месяца (зависит от размера) |
| Риски | Высокие (потеря бизнес-логики) | Низкие (сохраняется поведение) |
| Возможность параллельной разработки | Нет | Да |
| Требования к тестированию | Полное перетестирование | Инкрементальное тестирование |
Что входит в работу
- Аудит кодовой базы и составление плана миграции по приоритетам
- Расстановка nullable/nonnull аннотаций в ObjC-заголовках
- Поэтапная миграция: модели → сервисы → ViewModels → UI
- Перевод completion handlers на async/await
- Адаптация unit-тестов
- Code review и проверка ObjC/Swift boundary на каждом этапе
Сроки
| Объём кодовой базы | Ориентировочные сроки |
|---|---|
| До 10 000 строк ObjC | 2–3 недели |
| 10 000–50 000 строк | 1–2 месяца |
| 50 000+ строк | 2–3 месяца и более |
Сроки зависят от количества ObjC/Swift boundary точек, наличия тестов и готовности команды участвовать в ревью. Стоимость рассчитывается индивидуально после аудита кодовой базы. Закажите бесплатный аудит вашей кодовой базы — мы оценим объём работ и предложим план миграции. Свяжитесь с нами, чтобы обсудить детали.







