Уявіть кодову базу на Objective-C, яку потрібно перевести на Swift без зупинки розробки нових функцій. Ми використовуємо поетапну міграцію: файл за файлом, зберігаючи поведінку та не ламаючи бізнес-логіку. Повний перепис з нуля — ризикований шлях, який втрачає нюанси, не задокументовані в коді. Наш досвід міграції 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 помилки раніше рантайму. Джерело: досвід міграції банківського додатку
Порівняння підходів до міграції
| Критерій | Повний перепис | Поетапна міграція |
|---|---|---|
| Час | 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 точок, наявності тестів та готовності команди брати участь у рев'ю. Вартість розраховується індивідуально після аудиту кодової бази. Замовте безкоштовний аудит вашої кодової бази — ми оцінимо обсяг робіт та запропонуємо план міграції. Зв'яжіться з нами, щоб обговорити деталі.







