Как пройти собеседование по Swift: топ вопросов продвинутого уровня

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как пройти собеседование по Swift: топ вопросов продвинутого уровня

Собеседование на позицию iOS-разработчика — это не только проверка знания UIKit и SwiftUI. На продвинутом уровне рекрутеры и технические лиды хотят убедиться, что вы понимаете, как работает память, чем отличается class от struct в контексте reference counting, как правильно строить архитектуру с помощью протоколов и как управлять асинхронностью. В этой статье мы разберем ключевые темы, которые часто встречаются на собеседованиях по Swift уровня Middle и Senior.

1. Управление памятью и ARC (Automatic Reference Counting)

ARC — одна из самых частых тем на интервью. Вам нужно не просто знать теорию, но и уметь объяснить, как избежать утечек памяти.

1.1. Как работает ARC и какие циклы сильных ссылок бывают?

ARC автоматически подсчитывает количество сильных ссылок на объект класса. Как только счетчик становится равен нулю, объект удаляется из памяти. Проблема возникает, когда два объекта ссылаются друг на друга сильно — образуется цикл (retain cycle).

class Parent {
    var child: Child?
}

class Child {
    var parent: Parent?  // сильная ссылка обратно
}

var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent
// Оба объекта никогда не освободятся

1.2. Как разорвать цикл?

Используйте weak или unowned. weak — это опциональная ссылка, которая автоматически становится nil при удалении объекта. unowned — неопциональная, но предполагает, что объект будет жить как минимум столько же, сколько ссылающийся объект.

class Child {
    weak var parent: Parent?  // слабая ссылка
}

1.3. Как ARC работает с замыканиями?

Замыкания захватывают внешние переменные. Если замыкание хранится как свойство класса, а внутри него используется self, возникает цикл. Решение — использовать [weak self] или [unowned self].

class NetworkManager {
    var completionHandler: (() -> Void)?
    
    func fetchData() {
        completionHandler = { [weak self] in
            guard let self = self else { return }
            self.processData()
        }
    }
    
    func processData() { }
}

2. Замыкания и escaping vs non-escaping

Понимание разницы между escaping и non-escaping замыканиями — обязательный пункт для любой продвинутой позиции.

2.1. Что такое escaping замыкание?

Замыкание называется escaping, если оно может быть вызвано после возврата из функции. По умолчанию все замыкания non-escaping — компилятор знает, что они будут выполнены внутри тела функции. Для асинхронных операций (например, сетевых запросов) нужно явно указывать @escaping.

func performAsyncTask(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        // Имитация долгой задачи
        sleep(2)
        completion("Готово")
    }
}

2.2. Почему non-escaping замыкания быстрее?

Компилятор оптимизирует non-escaping замыкания — он может не копировать их в кучу (heap), а хранить на стеке. Также он может избежать подсчета ссылок, так как время жизни замыкания ограничено вызовом функции.

3. Дженерики и протоколы с associated types (PAT)

Дженерики — основа Swift. На собеседовании вас могут попросить написать обобщенную структуру или объяснить, как работают протоколы с ассоциированными типами.

3.1. Напишите generic очередь (Queue)

struct Queue<T> {
    private var elements: [T] = []
    
    mutating func enqueue(_ value: T) {
        elements.append(value)
    }
    
    mutating func dequeue() -> T? {
        guard !elements.isEmpty else { return nil }
        return elements.removeFirst()
    }
}

3.2. В чем отличие protocol от protocol с associatedtype?

Обычный протокол может использоваться как тип (например, [Drawable]). Протокол с associatedtype (PAT) — нет, потому что Swift не знает конкретный тип ассоциации. Для PAT используется стирание типа (type erasure) или some/any.

protocol Container {
    associatedtype Item
    mutating func add(_ item: Item)
}

// Использование через any Container
struct AnyContainer<T>: Container {
    var items: [T] = []
    mutating func add(_ item: T) { items.append(item) }
}

3.3. Что такое opaque types (some) и в чем их отличие от generics?

some (opaque type) говорит компилятору: «верни конкретный тип, который соответствует протоколу, но не раскрывай его». Дженерики же требуют указания типа на стороне вызывающего кода. Opaque types полезны для абстракции, например в SwiftUI.

func makeShape() -> some Shape {
    return Circle()
}

4. Асинхронность: Combine и async/await

Swift 5.5+ принес async/await, но Combine все еще активно используется в проектах. На собеседовании спросят и то, и другое.

4.1. Основы Combine: Publisher, Subscriber, Subject

Combine — это фреймворк для работы с асинхронными потоками данных. Основные компоненты:

  • Publisher — источник данных (например, URLSession.DataTaskPublisher)
  • Subscriber — потребитель данных (обычно sink или assign)
  • Subject — мост между императивным и реактивным кодом (PassthroughSubject, CurrentValueSubject)
import Combine

let publisher = PassthroughSubject<Int, Never>()
let subscription = publisher.sink { value in
    print("Получено: \(value)")
}
publisher.send(42)

4.2. async/await и structured concurrency

С async/await код становится линейным и читаемым. Важно понимать Task, TaskGroup и

Блоги

Рекомендаций по книгам