Урок по треку Swift и iOS 7 мин

Производительность и тестирование iOS-приложений

Instruments, отрисовка и hitches, запуск приложения, XCTest и Swift Testing, моки и UI-тесты.

На middle и senior собесах спрашивают не только «как писать», но и «как измерить и проверить». Здесь инструменты, метрики и стратегия тестов.

Что измерять

  • Запуск: время до первого кадра. Холодный старт (процесса нет) самый дорогой.
  • Плавность: рывки (hitches), когда кадр не уложился в бюджет. 60 Гц дают 16,7 мс на кадр, 120 Гц на ProMotion 8,3 мс.
  • Зависания (hangs): главный поток не отвечает на ввод заметное время (от ~250 мс).
  • Память: пиковое потребление и рост. При превышении лимита система убивает процесс (jetsam), и в обычных крэш-репортах этого не видно.
  • Батарея и сеть: фоновая активность, лишние запросы.

Измеряют на Release-сборке на реальном, лучше старом устройстве, а не в симуляторе.

Instruments

ШаблонВопрос
Time Profilerгде тратится процессорное время
Animation Hitchesкакие кадры опоздали и почему
App Launchчто происходит до первого кадра
Allocationsкакие объекты растут
Leaksутечки памяти
Networkкакие запросы и сколько данных

В поле данные собирает MetricKit: время запуска, hang rate, память, диагностику крэшей и зависаний со стеками. Похожие сводки есть в Xcode Organizer.

Типичные проблемы и решения

Картинки. Память декодированной картинки это ширина × высота × 4 байта, независимо от размера файла: фото 4000×3000 занимает 48 МБ. Картинки уменьшают до размера показа (downsampling через ImageIO), декодируют в фоне и держат в NSCache.

Запуск. До main работает загрузчик: каждая динамическая библиотека стоит времени, поэтому зависимости линкуют статически. После main в didFinishLaunching и до первого кадра делают только нужное первому экрану, SDK инициализируют лениво, первый экран показывают из кеша.

Списки. Никакой синхронной работы в конфигурации ячейки, тени с shadowPath, prefetching, точечные обновления вместо reloadData.

Гонки данных. Thread Sanitizer в схеме тестов ловит одновременный доступ без синхронизации. Main Thread Checker ловит обращения к UI не с главного потока. Режим Swift 6 находит многие гонки ещё при компиляции.

Тестирование

Пирамида: много юнит-тестов, меньше интеграционных, немного UI-тестов на ключевые сценарии.

Тестируемость закладывается архитектурой. View model получает зависимости через инициализатор и протоколы:

protocol ProfileService { func load() async throws -> Profile }

@MainActor final class ProfileViewModel {
    private let service: ProfileService
    init(service: ProfileService) { self.service = service }
}

Тестовые двойники:

  • стаб возвращает заготовленные ответы;
  • мок или спай записывает вызовы для проверки взаимодействия;
  • фейк это упрощённая рабочая реализация (база в памяти);
  • dummy только заполняет параметр.

Swift Testing (Xcode 16+):

import Testing

@Test func totalWithDiscount() {
    let cart = Cart(items: [.init(price: 1000)], discount: 10)
    #expect(cart.total == 900)
}

@Test(arguments: [0, 10, 50])
func discountNeverNegative(percent: Int) {
    #expect(Cart(items: [], discount: percent).total >= 0)
}

#expect показывает значения при провале, #require прерывает тест, тесты по умолчанию параллельны. XCTest остаётся для UI-тестов и старого кода, async-тесты пишутся как func test() async throws.

Время в тестах. Вместо реального ожидания внедряют Clock и в тесте продвигают время вручную: тест быстрый и стабильный.

Сеть. Подкласс URLProtocol перехватывает запросы тестовой сессии и отдаёт заготовленные ответы, проверяя весь путь от запроса до декодирования.

UI-тесты (XCUITest) работают в отдельном процессе через дерево доступности. Элементы ищут по accessibilityIdentifier, а не по тексту. Ими покрывают только критичные пути: вход, покупку, онбординг.

Snapshot-тесты сравнивают скриншоты компонентов с эталоном, полезны для дизайн-системы.

Утечки в тестах: слабая ссылка на объект и проверка в teardown, что она стала nil.

Частые ошибки

  • Профилировать Debug-сборку в симуляторе.
  • Ждать в тестах реальные секунды.
  • Искать элементы UI-тестов по тексту.
  • Перезапускать нестабильные тесты вместо исправления.

Что спросят на собесе

  • Как найти, почему экран медленно открывается?
  • Как ускорить холодный старт?
  • Сколько памяти занимает картинка и как это уменьшить?
  • Как вы тестируете view model, сеть, код со временем?
  • Чем Swift Testing отличается от XCTest?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.