Введение
Собеседование на позицию iOS/macOS разработчика — это не только проверка знания фреймворков UIKit или SwiftUI. В первую очередь работодатель оценивает ваше понимание языка Swift: его синтаксиса, особенностей управления памятью, работы с многопоточностью и типовой системы. Даже если вы уверенно пишете приложения, без теоретической базы легко провалить техническое интервью.
В этой статье мы собрали топ вопросов, которые чаще всего задают на собеседованиях по Swift в 2025 году. Каждый вопрос сопровождается подробным объяснением и примером кода. Материал будет полезен как джуниорам, так и разработчикам с опытом, желающим освежить знания.
1. Опциональные типы (Optionals) и безопасность
Почему Swift использует Optionals?
Swift — язык с строгой типизацией, и опциональные типы — его ключевая фича для безопасной работы с nil. В отличие от Objective-C, где nil — это просто нулевой указатель, в Swift Optional — это перечисление (enum) с двумя случаями: none (nil) и some(Wrapped). Это заставляет разработчика явно обрабатывать отсутствие значения, предотвращая краши.
Как извлечь значение из Optional?
Есть несколько способов:
- Force unwrapping — оператор
!. Используется только когда вы точно уверены, что значение не nil. - Optional binding — конструкция
if letилиguard let. - Nil-coalescing operator —
??для задания значения по умолчанию.
var name: String? = "Alice"
// Force unwrapping (опасно!)
print(name!)
// Optional binding
if let unwrappedName = name {
print("Hello, \(unwrappedName)")
}
// Nil-coalescing
let displayName = name ?? "Guest"
print(displayName)
Что такое Implicitly Unwrapped Optionals (IUO)?
Это опциональный тип, который автоматически извлекается при обращении. Объявляется с помощью ! после типа. Используется в основном для свойств, которые инициализируются после init (например, IBOutlet в UIKit). Однако в современном Swift (5.x) IUO ведут себя как обычные Optionals, и компилятор предупреждает о рисках.
2. Управление памятью: ARC и циклы сильных ссылок
Как работает ARC в Swift?
Automatic Reference Counting (ARC) — это механизм подсчета ссылок. Каждый экземпляр класса имеет счетчик ссылок. Когда счетчик падает до нуля, объект удаляется из памяти. ARC работает только с reference types (классы, замыкания). Value types (структуры, перечисления) хранятся в стеке и управляются копированием.
Что такое цикл сильных ссылок (retain cycle)?
Цикл возникает, когда два объекта ссылаются друг на друга через сильные ссылки, и ARC не может их освободить. Это приводит к утечке памяти. Решение — использовать слабые (weak) или бесхозные (unowned) ссылки.
class Person {
let name: String
var apartment: Apartment?
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class Apartment {
let unit: String
weak var tenant: Person? // weak — предотвращает цикл
init(unit: String) { self.unit = unit }
deinit { print("Apartment \(unit) deallocated") }
}
var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil
unit4A = nil
// Оба объекта будут освобождены
Когда использовать weak, а когда unowned?
- weak — всегда optional, автоматически становится nil при освобождении объекта. Используется, когда время жизни ссылки может быть короче, чем у владельца.
- unowned — non-optional, предполагает, что объект будет жить столько же или дольше. Опасен: если объект освобожден, обращение к unowned вызовет crash.
3. Замыкания (Closures) и захват значений
Чем замыкания отличаются от функций?
Замыкания — это анонимные функции, которые могут захватывать и хранить ссылки на переменные из окружающего контекста. В Swift функции — это частный случай замыканий (имеют имя). Замыкания бывают трех видов: глобальные функции (с именем, не захватывают), вложенные функции (захватывают из внешней функции) и выражения-замыкания (анонимные).
Что такое @escaping closure?
По умолчанию замыкание является non-escaping — оно выполняется внутри тела функции и не сохраняется за её пределами. Если замыкание может быть вызвано после возврата функции (например, сохранено в свойство или передано в асинхронный колбэк), его нужно пометить как @escaping. Это ключевое слово напоминает разработчику о возможном retain cycle.
class NetworkManager {
var completionHandler: (() -> Void)?
func fetchData(completion: @escaping () -> Void) {
// Сохраняем замыкание для асинхронного вызова
self.completionHandler = completion
DispatchQueue.global().async {
// ... работа ...
DispatchQueue.main.async {
completion()
}
}
}
deinit {
print("NetworkManager deallocated")
}
}
Как избежать retain cycle в замыканиях?
Если замыкание захватывает self и сохраняется в свойстве объекта, возникает цикл. Решение — использовать [weak self] или [unowned self] в списке захвата.
class ViewController: UIViewController {
var onButtonTap: (() -> Void)?
func setupHandler() {
// [weak self] — обязателен, чтобы избежать цикла
onButtonTap = { [weak self] in
guard let self = self else { return }
self.view.backgroundColor = .red
}
}
}
4. Многопоточность: GCD и async/await
В чем разница между serial и concurrent queue?
- Serial queue — выполняет задачи последовательно, одну за другой. Гарантирует порядок выполнения.
- Concurrent queue — запускает несколько задач одновременно (на разных потоках). Порядок не гарантирован.
GCD (Grand Central Dispatch