Собеседование iOS-разработчика проверяет систему типов Swift, понимание ARC и умение рассуждать о состоянии интерфейса — плюс обязательно найдётся вопрос, где retain cycle прячется на виду. Ниже — вопросы, которые задают чаще всего, каждый с эталонным ответом.
К каждому вопросу — эталонный ответ, с которым можно сравнить свой.
1
Какую проблему решают опционалы и как безопасно их разворачивать?
Ответ
Опционал делает «значения может не быть» частью типа, поэтому компилятор заставляет обработать пустой случай, а не падать в рантайме, как это делает нулевой указатель. Безопасное разворачивание — это if let или guard let, когда значение нужно дальше по коду, оператор ?? для значения по умолчанию и опциональная цепочка, если интересен только успешный путь. Force unwrap через ! — это обещание компилятору, что значение есть: допустимо для outlet-ов и ошибок программиста, но никогда для данных из сети.
2
В чём разница между struct и class в Swift? Что берёте по умолчанию?
Ответ
Struct — значимый тип: присваивание копирует его, наследования нет, и по возможности он размещается на стеке. Class — ссылочный тип с идентичностью, наследованием, deinit и накладными расходами ARC. Конвенция Swift — начинать со struct и переходить на class только когда нужна ссылочная семантика: разделяемое изменяемое состояние, совместимость с Objective-C или жизненный цикл, за которым нужно наблюдать.
3
Как работает ARC и из-за чего возникает retain cycle?
Ответ
ARC считает количество сильных ссылок на объект и освобождает его, когда счётчик обнуляется — это вставленные на этапе компиляции вызовы retain/release, а не сборщик мусора в рантайме. Retain cycle возникает, когда два объекта держат сильные ссылки друг на друга и ни один счётчик не доходит до нуля: классика — вью-контроллер владеет замыканием, которое сильно захватывает self. Разрывается через [weak self] или [unowned self] в списке захвата.
4
Когда использовать weak, а когда unowned?
Ответ
Оба не увеличивают счётчик ссылок. weak делает ссылку опциональной и обнуляет её при освобождении объекта, поэтому безопасен, если другой объект может умереть первым — делегаты это стандартный пример. unowned неопционален и предполагает, что объект переживёт вас; если предположение нарушится, приложение упадёт. unowned берут только когда связь жизненных циклов действительно гарантирована, во всех сомнительных случаях — weak.
5
В чём разница между @State, @Binding, @StateObject и @ObservedObject?
Ответ
@State — локальное состояние значимого типа, которым владеет вью; SwiftUI хранит его вне структуры, поэтому оно переживает перерисовки. @Binding — ссылка на чужое состояние с правом записи, именно так дочерний вью меняет значение родителя. @StateObject создаёт и владеет ссылочным observable-объектом и инициализируется ровно один раз. @ObservedObject наблюдает за объектом, которым владеет кто-то другой — если поставить его там, где нужен @StateObject, объект будет пересоздаваться на каждой перерисовке и молча терять состояние.
6
Как SwiftUI решает, что вью нужно перерисовать?
Ответ
Вью — это лёгкая структура, описывающая интерфейс; SwiftUI пересобирает её body всякий раз, когда меняется прочитанная ею зависимость, затем сравнивает результат с предыдущим деревом и применяет только различия. Зависимости — это те property wrapper-ы, которые вью реально читает, плюс Environment. Проблемы производительности почти всегда идут от состояния, поднятого слишком высоко по дереву: не связанное изменение инвалидирует большое поддерево.
7
Что меняет async/await по сравнению с completion-хендлерами и GCD?
Ответ
async/await позволяет читать асинхронный код сверху вниз, поэтому обработка ошибок идёт обычным try/catch, нет «пирамиды вложенности» и забытых веток завершения. Компилятор следит, чтобы await вызывался в async-контексте, а структурная конкурентность привязывает жизнь дочерних задач к родительской, так что отмена распространяется автоматически. GCD остаётся актуальным для легаси и тонкого управления очередями, но новый код стоит писать на async/await, оставив Combine для настоящих потоков значений во времени.
8
Что такое escaping-замыкание и зачем нужно это ключевое слово?
Ответ
Замыкание считается escaping, если оно сохраняется и вызывается уже после возврата из функции — например, completion-хендлер сетевого запроса. Swift требует пометки @escaping, потому что не-escaping замыкание можно агрессивнее оптимизировать и оно не может случайно сильно захватить self. Эта пометка ещё и сигнал подумать о семантике захвата: именно в escaping-замыканиях рождаются retain cycle.
9
Разберите жизненный цикл UIViewController. Где размещать код вёрстки?
Ответ
viewDidLoad выполняется один раз после загрузки иерархии вью и предназначен для разовой настройки. viewWillAppear и viewDidAppear вызываются при каждом показе — туда идёт то, что должно повторяться: обновление данных, запуск анимации. Вёрстка, зависящая от финальных размеров, живёт в viewDidLayoutSubviews, потому что в viewDidLoad границы ещё не окончательные — вычисление фреймов там очень частый баг, который проявляется только на другом размере экрана.
10
Как выбрать между Core Data, SwiftData и просто файлами?
Ответ
Core Data оправдывает свою сложность, когда данные реляционные и нужны запросы, ленивая подгрузка и отслеживание изменений на большом графе объектов. SwiftData — современная нативная для Swift обёртка над тем же стеком, дефолт для новых приложений на свежих версиях ОС. Для десятка настроек берут UserDefaults, а для кеша декодированных моделей обычные Codable-файлы или лёгкое key-value хранилище понятнее, чем полноценный persistent container.
11
Как диагностировать медленный запуск или утечку памяти?
Ответ
Для времени запуска берут шаблон App Launch в Instruments и смотрят, что выполняется до первого кадра; обычные виновники — синхронная работа с диском или сетью в didFinishLaunching и тяжёлый граф зависимостей, собираемый заранее. Для утечек Memory Graph Debugger показывает объекты, которые должны были освободиться, и того, кто на них ещё ссылается, — это напрямую вскрывает retain cycle. Инструмент Leaks ловит классические утечки, но граф удобнее для циклов, потому что он называет удерживающую ссылку.
12
Что вы тестируете в iOS, а что сознательно не тестируете?
Ответ
Юнит-тестами покрывают бизнес-логику, вью-модели и чистые преобразования — всё, у чего есть вход и выход и нет зависимости от UIKit. XCUITest берут на небольшое число критичных сквозных сценариев вроде входа и покупки, потому что UI-тесты медленные и хрупкие в больших количествах. Обычно не окупается тестирование поведения фреймворков Apple и попиксельной вёрстки; снапшот-тесты тут помогают, но ломаются на каждой смене версии ОС, поэтому их держат мало и осознанно.
🦎
Прочитать ответы недостаточно
На собеседовании ты говоришь вслух под давлением. Кем задаст эти же вопросы, оценит каждый ответ и покажет, что именно подтянуть.
Пройти интервью iOS (Swift) →Бесплатно · 3 интервью в месяц