objc-retain.com

Как подготовить legacy-приложение на Objective-C к дальнейшей разработке

Мобильные решения
Как подготовить legacy-приложение на Objective-C к дальнейшей разработке

Legacy-приложение на Objective-C можно безболезненно развивать дальше, если сначала привести в порядок основу: сборку, зависимости, архитектурные границы, тесты и правила изменения кода. Самая частая ошибка — сразу начинать переписывать экраны, хотя сначала нужно сделать кодовую базу управляемой и предсказуемой. На практике, особенно в PropTech, где приложение для риэлтора должно стабильно работать на показе, а синхронизация каталогов объектов идёт в реальном времени, хаотичный рефакторинг без подготовки гарантированно приводит к регрессиям в самые неподходящие моменты.

Зачем вообще готовить legacy-код перед новыми фичами

Старое iOS-приложение обычно страдает не от одного большого дефекта, а от набора мелких проблем: длинные методы, тесные связи между экранами и сервисами, устаревшие зависимости, слабое покрытие тестами и опасные изменения памяти после миграций между manual retain/release и ARC. В контексте недвижимости это выливается в конкретные бизнес-риски: если приложение для агента падает при открытии карточки объекта или теряет данные офлайн-бронирования, страдает не только пользовательский опыт, но и репутация агентства, а иногда и сама сделка.

Если просто добавлять новые функции поверх такого кода, каждая доработка начинает стоить дороже: растет риск регрессий, тормозится выпуск, а команда боится трогать «хрупкие» места. В одном проекте для застройщика мы наблюдали, как добавление простого фильтра по цене в каталог квартир вызывало каскадное падение экранов из-за неявной зависимости от синглтона, который хранил состояние фильтров в ручном режиме управления памятью.

С чего начать: короткий аудит кодовой базы

Перед рефакторингом нужен не «идеальный» реестр проблем, а практичная карта рисков. Начинать лучше с четырех вещей: сборка, краш-статистика, самые изменяемые модули и самые дорогие по времени участки. Когда мы аудировали приложение для риэлторской сети, первым делом обнаружили, что проект собирается только на конкретной версии Xcode с устаревшим CocoaPods, а треть зависимостей уже deprecated и конфликтует с iOS SDK. Без исправления этого фундамента любой рефакторинг был бы обречён.

Что проверить в первую очередь

  • Проект собирается на текущем Xcode и актуальном iOS SDK.
  • Есть ли предупреждения компилятора, которые уже стали нормой и скрывают реальные ошибки.
  • Какие экраны или сервисы меняли чаще всего за последние релизы.
  • Где чаще всего возникают крэши, зависания и утечки памяти — особенно в сценариях, связанных с загрузкой изображений планировок или синхронизацией больших JSON-каталогов.
  • Какие сторонние библиотеки давно не обновлялись.
  • Есть ли завязка на устаревшие API, которые уже подводят при обновлении iOS.

Практичный результат аудита

После аудита должен получиться короткий список:

  • что мешает выпускать релизы сейчас;
  • что опасно трогать — например, модуль кэширования изображений с ручным retain/release, который держит в памяти десятки мегабайт фотографий объектов;
  • что можно отделить и переписать первым — часто это сервис авторизации или парсер ответа от CRM недвижимости;
  • что лучше оставить как есть до появления тестов и изоляции.

Как понять, что код уже пора модернизировать

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

Признак Что это значит на практике Риск
Большие view controller’ы UI, бизнес-логика и работа с сетью смешаны в одном месте — например, контроллер карточки квартиры сам делает запрос к API, маппит JSON и форматирует цену Любое изменение ломает соседние сценарии
Повторяющийся код Одинаковые проверки, форматирование, запросы и маппинг размазаны по проекту — типично для каталогов, где каждый экран списка объектов дублирует логику пагинации Высокая стоимость поддержки
Слабые тесты Нет быстрого способа проверить, что поведение не изменилось — особенно опасно для сценариев бронирования или расчёта ипотеки Регрессии после каждой правки
Устаревшие библиотеки Зависимости конфликтуют с новым SDK или Xcode — например, старая версия AFNetworking не поддерживает ATS, а без неё не пройти аудит безопасности для банковского модуля ипотеки Блокировка обновлений
Много ручного memory management Повышенный риск утечек, dangling pointers и неожиданных циклов удержания — часто встречается в модулях работы с изображениями и офлайн-хранилищами объектов Сложнее безопасно вносить изменения
Монолитная структура Модули зависят друг от друга почти без границ — например, экран поиска объектов напрямую дёргает методы из контроллера карточки Нельзя менять по частям

Базовый план подготовки legacy-приложения

Лучше идти не по принципу «переписать всё», а по цепочке, где каждый шаг уменьшает риск следующего. В проектах для рынка недвижимости это особенно важно, потому что приложение часто обслуживает одновременно и риэлторов, и клиентов, и любая остановка релизов бьёт по бизнесу.

1. Сделать сборку воспроизводимой

Первый шаг — убедиться, что проект стабильно собирается в чистом окружении. Если сборка зависит от «магии» локальной машины, дальнейшая работа будет ломаться на ровном месте. В одном кейсе приложение для девелопера собиралось только на MacBook конкретного разработчика, потому что часть путей к библиотекам была прописана абсолютно. Перенос в CI выявил десяток скрытых проблем.

Что стоит сделать:

  • зафиксировать версии Xcode и инструментов;
  • убрать неиспользуемые схемы и мертвые target’ы;
  • проверить конфигурации Debug/Release;
  • описать сборку в CI;
  • зафиксировать зависимости и порядок их установки (например, через Podfile.lock и строгую версионность).

2. Добавить минимальный уровень автоматических тестов

Не нужно покрывать весь проект сразу. Достаточно начать с «страховочных» тестов на самые критичные сценарии: авторизация, список объектов, карточка объекта, поиск, офлайн-режим, синхронизация. Для агентского приложения, где риэлтор офлайн просматривает каталог и бронирует квартиры, тесты на сохранение и восстановление данных после потери сети — это не роскошь, а необходимость.

Полезный ориентир:

  • модульные тесты — для логики и преобразований данных (например, расчёт комиссии или форматирование площади);
  • интеграционные — для связки сервисов и моделей (получение списка объектов из API и маппинг в локальное хранилище);
  • UI-тесты — только для ключевых пользовательских путей (поиск объекта, открытие карточки, отправка заявки).

3. Сначала выделить границы, потом переписывать

Если код перемешан, сначала нужно отделить ответственность. В Objective-C это особенно важно: старые приложения часто строились по принципу «все в контроллере». Гораздо безопаснее сначала вынести логику в отдельные классы, чем сразу менять технологический стек. Например, в проекте для управления арендой мы выделили сервис синхронизации с календарём бронирований в отдельный класс, и только после этого смогли безболезненно обновить сетевой слой.

Какие зоны стоит рефакторить первыми

Приоритизация решает больше, чем объем работы. Если начать с самого «грязного» кода, можно увязнуть на недели, не получив ощутимого бизнес-эффекта. Лучше выбирать участки, которые одновременно и часто меняются, и несут высокие риски.

Лучшие кандидаты на первый рефакторинг

  • Длинные методы, которые сложно читать и тестировать — например, метод обновления UI после ответа сервера, где вперемешку парсинг, валидация и форматирование.
  • Классы, которые делают слишком много всего сразу — контроллер, который и загружает данные, и управляет кэшем изображений, и считает ипотеку.
  • Дублирующийся код в нескольких экранах — типично для каталогов, где каждый список объектов повторяет логику сортировки и фильтрации.
  • Сетевой слой без единого формата ошибок — когда каждый запрос возвращает ошибку по-своему, и UI не может унифицировать показ алертов.
  • Логика форматирования, вычислений и преобразования данных, спрятанная в UI — например, форматирование цены в рублях прямо во view.
  • Код, завязанный на deprecated API — особенно критично для работы с геолокацией или картами, которые часто меняются в iOS.
  • Места с повторяющимися багами — если экран списка объектов регулярно падает при скролле, проблема скорее всего в общем механизме повторного использования ячеек и управления памятью.

Что обычно даёт быстрый эффект

  • extract method — разбить большой метод на маленькие;
  • extract class — вынести отдельную ответственность (например, парсер ответа API в отдельный объект);
  • move method / move field — перенести поведение туда, где ему место;
  • introduce constant — заменить магические числа и строки (идентификаторы типов недвижимости, ключи JSON);
  • simplify conditionals — упростить вложенные условия, которые часто возникают при проверке доступности объектов в разных статусах.

ARC, memory management и что делать со старым retain/release

Если проект пережил несколько эпох Objective-C, в нём часто смешаны старые паттерны управления памятью и более современный код. Здесь главная цель — не «перевести всё сразу», а убрать самые опасные места и убедиться, что поведение не изменилось. В PropTech-приложениях утечки памяти особенно коварны: при длительном использовании (риэлтор может держать приложение открытым весь день) даже небольшая утечка в кэше изображений планировок или в подписке на уведомления о новых объектах приводит к вылету по memory pressure в самый неподходящий момент.

Что проверить

  • есть ли участки с ручным retain/release/autorelease;
  • встречаются ли циклы удержания между контроллерами, блоками и делегатами — классика: контроллер карточки объекта сильно ссылается на блок, а блок захватывает контроллер;
  • корректно ли используются weak-ссылки — особенно в блоках обработки ответов сети;
  • нет ли утечек в кешах, singleton’ах и подписках на уведомления — например, синглтон менеджера объектов недвижимости может накапливать словари с объектами, которые уже не нужны;
  • не возвращает ли код освобожденные объекты через старые паттерны — такое бывает в самописных кэшах, где после очистки памяти объект может быть пересоздан, но указатель остаётся висячим.

Безопасный подход

  • сначала включить анализ утечек и предупреждений (статический анализатор Xcode);
  • затем локально мигрировать отдельные модули на ARC — например, модуль работы с избранными объектами, который уже покрыт тестами;
  • после каждой правки прогонять тесты и запускать сценарии с памятью под нагрузкой — симулировать долгую работу с приложением, открытие множества карточек, переключение между разделами;
  • не смешивать крупный рефакторинг памяти с одновременной архитектурной перестройкой — слишком высок риск потерять контроль над побочными эффектами.

Как выстроить архитектуру, если приложение уже выросло

Legacy-приложение редко становится проще само. Если продолжать писать в стиле «новый экран — новый массив if’ов», со временем вся кодовая база превращается в набор взаимозависимых файлов. В мире недвижимости это приводит к тому, что добавление нового типа объекта (например, паркинга или кладовки) требует правок в десятке мест, от контроллера списка до модуля расчёта платежей.

Рабочая цель

Не обязательно сразу переходить на сложную «идеальную» архитектуру. Достаточно добиться трех вещей:

  • у UI нет прямого доступа к тяжелой бизнес-логике — контроллер не должен знать, как именно вычисляется ипотечный платёж или проверяется доступность квартиры;
  • сетевой слой отделен от представления — запросы и парсинг ответов не лежат в view controller’ах;
  • состояние экрана формируется в одном месте, а не размазывается по всему контроллеру — например, отдельный объект, который собирает данные для отображения карточки объекта из разных источников.

Практический ориентир для Objective-C

  • оставить UIKit там, где он уже стабилен — не стоит трогать работающие экраны без крайней необходимости;
  • постепенно выносить бизнес-логику в отдельные сервисы и модели — сервис объектов недвижимости, сервис бронирования, сервис расчёта комиссии;
  • для новых модулей вводить более строгие границы — например, модуль виртуальных туров или AR-просмотра квартиры сразу проектировать с чистыми протоколами и dependency injection;
  • не тащить новые фичи в старые монолитные контроллеры без необходимости — если нужно добавить ипотечный калькулятор, лучше создать отдельный модуль и интегрировать его через протокол, а не раздувать существующий контроллер карточки.

Как безопасно вводить Swift в Objective-C-проект

Если дальнейшая разработка будет идти в смешанном стеке, лучший путь — не переписывать весь проект, а подключать Swift точечно, по новым модулям. В PropTech это хорошо ложится на типовые сценарии: старый код на Objective-C продолжает обслуживать каталоги и синхронизацию, а новые фичи, вроде чата с агентом или интерактивных планировок, пишутся на Swift.

Хороший сценарий

  • новый функциональный блок пишется на Swift — например, модуль электронной подписи договора;
  • Objective-C продолжает обслуживать старый стабильный код — все экраны списков и карточек объектов;
  • мост между языками держится тонким и понятным — через bridging header и протоколы, без сложных обёрток;
  • критические сценарии тестируются на обеих сторонах стека — особенно важно для сквозных процессов, вроде оформления сделки, где участвуют и старые, и новые модули.

Когда не стоит торопиться

  • если в проекте нет тестов — любое изменение в смешанной кодовой базе без тестовой подушки становится рискованным;
  • если модуль и так нестабилен — сначала стабилизируем, потом переписываем;
  • если команда не умеет сопровождать mixed codebase — нужно сначала нарастить экспертизу, иначе количество багов на стыке языков может перевесить выгоду;
  • если нужно срочно выпускать релизы без риска — в горячие периоды (например, запуск продаж нового корпуса) лучше отложить языковые миграции.

Зависимости, сборка и обновления SDK

Устаревшие библиотеки — частая причина, по которой legacy-проект перестает нормально жить на новых версиях iOS. В приложениях для недвижимости это может быть критично: если библиотека для работы с картами не обновляется, а Apple требует использовать новую версию MapKit, приложение просто не пройдёт ревью.

Что делать с зависимостями

  • составить список всех third-party SDK;
  • отметить, какие из них поддерживаются — и какие уже заброшены;
  • проверить, какие библиотеки тянут deprecated API — например, старый способ запроса геолокации;
  • удалить дублирующие решения — нередко в проекте одновременно живут две библиотеки для работы с изображениями;
  • закрепить способ управления зависимостями и версионность — CocoaPods, Swift Package Manager или Carthage, но единообразно;
  • не обновлять все сразу, а двигаться по одной цепочке изменений — сначала обновить сетевую библиотеку, убедиться, что ничего не сломалось, затем следующую.

Чего избегать

  • массового обновления пакетов перед релизом;
  • замены менеджера зависимостей без причины — переход с CocoaPods на SPM только ради моды может отнять недели и принести новые проблемы;
  • одновременного рефакторинга сети, UI и storage — слишком много переменных, сложно локализовать источник бага.

Чек-лист готовности legacy-приложения к дальнейшей разработке

Используйте этот список как минимальный критерий «можно развивать дальше». Для приложения, которое ежедневно используют сотни риэлторов, каждый пункт — это страховка от потери данных или срыва показов.

  • Проект стабильно собирается в чистом окружении.
  • Есть CI-сборка и базовые автотесты.
  • Критичные пользовательские сценарии покрыты тестами.
  • Известны самые рискованные модули.
  • Архитектурные границы хотя бы частично выделены.
  • Убраны самые опасные memory-проблемы.
  • Устаревшие зависимости зафиксированы и описаны.
  • Новые фичи можно добавлять без изменения половины приложения.
  • Команда понимает, какие части системы трогать нельзя без подготовки.

Типовые ошибки при подготовке legacy Objective-C-приложения

1. Переписывать вместо рефакторинга

Если бизнес-функция уже работает, чаще выгоднее стабилизировать и постепенно выносить куски кода, чем устраивать большой rewrite. Полная переписка модуля каталога объектов с нуля может занять месяцы, а за это время бизнес потребует новые фильтры и интеграции — и старый код продолжит обрастать костылями.

2. Сначала менять технологию, потом искать тесты

Без тестов миграция превращается в гадание. Даже простые проверки дают больше пользы, чем «красивый» новый слой без защиты. Видел проект, где решили перевести сетевой слой на Swift, не имея ни одного интеграционного теста — в результате половина запросов к CRM недвижимости падала с ошибками маппинга, и релиз откатили.

3. Лечить симптом, а не причину

Если падает конкретный экран, проблема может быть не в нём, а в общем сервисе, модели или потоке данных. Например, экран списка объектов может вылетать из-за того, что сервис синхронизации возвращает битые данные при плохом соединении, а не из-за ошибки в самом контроллере.

4. Делать все сразу

Legacy-код плохо переносит крупные одномоментные изменения. Лучше маленькие законченные шаги с понятным эффектом. Попытка одновременно обновить все зависимости, мигрировать на ARC и переделать архитектуру обычно заканчивается тем, что приложение перестаёт собираться на недели, а команда теряет веру в успех.

Пошаговый план на 30 дней

Первая неделя

  • собрать информацию о сборке, зависимостях и крашах;
  • зафиксировать проблемные модули;
  • определить топ-5 сценариев, которые нельзя сломать (например, просмотр карточки объекта, отправка заявки на бронирование, офлайн-доступ к избранному).

Вторая неделя

  • привести проект к воспроизводимой сборке;
  • добавить базовый CI;
  • написать первые тесты на критические сценарии — хотя бы модульные на ключевую логику и один интеграционный на сквозной сценарий.

Третья неделя

  • выделить один наиболее рискованный модуль — например, сервис синхронизации каталога;
  • вынести из него часть логики — отделить парсинг ответа от сохранения в БД;
  • сократить связность между UI и бизнес-правилами — контроллер списка объектов не должен знать, как именно обновляется локальное хранилище.

Четвертая неделя

  • обновить одну зависимость или один слой — например, сетевую библиотеку или модуль кэширования изображений;
  • проверить регрессии — пройтись по критическим сценариям вручную и прогонять автотесты;
  • задокументировать правила для следующих изменений — какие модули трогать только с тестами, какие зависимости обновлять только после проверки совместимости.

Вывод

Подготовка legacy-приложения на Objective-C к дальнейшей разработке — это не разовая «уборка», а создание безопасной среды для будущих изменений. Сначала нужно сделать сборку воспроизводимой, добавить тестовую защиту, отделить ответственность модулей, снизить риски памяти и только потом двигаться к более крупной модернизации. В PropTech, где приложение — это рабочий инструмент риэлтора, а не просто витрина, такой подход окупается стабильностью релизов и уверенностью, что новая функция не обрушит продажи.

Если приложение уже живет в продакшене, лучший путь почти всегда один: не ломать основу, а поэтапно превратить хаотичный код в систему, которую можно развивать без страха перед каждым новым релизом.

FAQ

С чего начать, если кодовая база очень старая?

С аудита сборки, крашей, зависимостей и самых часто изменяемых модулей. Это даст приоритеты и уберет хаос. Даже в проекте десятилетней давности можно найти точку опоры — обычно это модуль, который работает стабильно, и его можно взять за образец для наведения порядка.

Нужно ли сразу переводить проект на Swift?

Нет. Безопаснее вводить Swift точечно, по новым модулям, сохраняя работающий Objective-C-код. Особенно если в проекте ещё много ручного управления памятью — сначала лучше причесать старый код, а потом аккуратно подключать Swift.

Можно ли подготовить legacy-проект без полной переписки?

Да. Обычно достаточно рефакторинга, тестов, выделения границ и постепенной модернизации зависимостей. Полная переписка — это крайняя мера, когда поддерживать существующий код уже дороже, чем создать новый, но до этого состояния проект лучше не доводить.

Что важнее всего перед дальнейшей разработкой?

Стабильная сборка, базовые тесты и понятные архитектурные границы. Без этого любое изменение будет рискованным. Если вы можете ответить на вопрос «что точно сломается, если я поменяю этот класс?», значит, вы уже на правильном пути.