Вы не узнаете об этом из курса или блогов. Эти знания получены из реальных ошибок в продакшене, работы по ночам и того одного сбоя, который мы не могли воспроизвести в течение нескольких недель.
Вот 10 ловушек Swift, которые кажутся безобидными — пока не настигнут вас.
1. Сильные циклы ссылок в замыканиях
Замыкания по умолчанию захватывают self. Если вы не используете [weak self], вы можете получить цикл удержания и утечку памяти.
class ProfileViewModel {
var onUpdate: (() -> Void)?
func setup() {
onUpdate = {
self.doSomething() // ⚠️ This captures self strongly
}
}
}
Исправление:
onUpdate = { [weak self] in
self?.doSomething()
}
2. Принудительное развертывание Optional
Это очевидно, но мы все еще часто видим это в коде:
let name: String? = getName() print(name!) // Crashes if nil
Даже «безопасные» места могут подвести вас — например, повторное использование ячеек в UITableView.
Вместо этого используйте guard let или if let. Никогда не доверяйте данным слепо.
3. Неявно разворачиваемые Optional (String!)
Они кажутся удобными. Но они вызывают сбой, как и !, если не настроены правильно.
var token: String! print(token.count) // If token is nil, boom.
По умолчанию используется ?. Используйте ! только в том случае, если вы абсолютно уверены, что он инициализирован перед использованием (например, инжектирован через сториборд).
4. Не помеченные как final классы
По умолчанию каждый класс в Swift может быть подклассом. Это может привести к снижению производительности из-за динамической диспетчеризации.
Используйте final, когда подклассы не нужны. Компилятор будет оптимизировать код лучше.
final class UserManager {
// Now faster method calls, no subclassing allowed
}
5. Отсутствие weak у делегатов
Классическая ошибка в конфигурациях MVC или MVVM.
protocol MyDelegate: AnyObject {
func didUpdate()
}
class MyController {
var delegate: MyDelegate? // ⚠️ Should be weak
}
Всегда объявляйте свойства делегатов как weak, чтобы избежать циклов удержания:
weak var delegate: MyDelegate?
6. Бездумное использование DispatchQueue.main.async
Да, вам нужно обновлять пользовательский интерфейс в основном потоке. Но если вы слепо обернете все в DispatchQueue.main.async, вы рискуете столкнуться с условиями гонки или задержками обновлений.
DispatchQueue.main.async {
self.label.text = "Updated"
}
Используйте это только в том случае, если вы уверены, что уже не находитесь в главном потоке.
7. Путаница между типами значений и ссылок
Структуры (такие как User) копируются при присваивании. Классы совместно используются по ссылке.
struct User {
var name: String
}
var a = User(name: "Abhinav")
var b = a
b.name = "Singh"
print(a.name) // Still "Abhinav"
Знайте, когда вам нужна неизменяемость (структура) и когда — общее состояние (класс). Их смешивание приводит к неприятным ошибкам.
8. Неправильное использование Codable
Codable в Swift — это замечательно… до тех пор, пока не изменится структура JSON и декодирование без предупреждения не завершится с ошибкой.
struct User: Codable { let id: Int let name: String }
Если бэкэнд отправит user_id вместо id, декодирование завершится сбоем.
Используйте CodingKeys явно, когда поля различаются:
enum CodingKeys: String, CodingKey {
case id = "user_id"
case name
}
9. Неправильное использование @Published в SwiftUI/Combine
Простое добавление @Published не вызывает обновление пользовательского интерфейса вне основного потока или если обновление происходит косвенно.
@Published var name: String = ""
DispatchQueue.global().async {
self.name = "New" // ⚠️ No UI update
}
Всегда обновляйте свойства @Published в главном потоке:
DispatchQueue.main.async {
self.name = "New"
}
10. Путаница между Any и AnyObject
Они кажутся похожими, но на самом деле это не так. Any означает любой тип. AnyObject означает любой тип класса.
func handle(data: Any) { ... } // Can be Int, String, Class, etc.
func handleObject(data: AnyObject) { ... } // Only class instances
Будьте точны в том, что вы используете. Использование Any может привести к путанице при преобразовании типов во время выполнения.
Пример: ошибочная настройка
Давайте визуализируем типичную настройку утечки памяти:
[ViewController] --> owns --> [ViewModel] --> owns closure --> [ViewController]
Если это замыкание захватывает self → цикл удержания → ViewController никогда не деаллоцируется.
Исправьте с помощью [weak self] в закрытии, или лучше: сделайте так, чтобы владелец замыкания не владел источником.
Заключительные мысли
Swift — мощный язык, но он не является безошибочным.
Компилятор помогает, но везде есть скрытые ловушки. Большинство из этих ловушек не приведут к сбою в первый день — они проявятся через несколько недель, когда ваше приложение начнет вести себя странно или излишне потреблять память.
Будьте параноиком на раннем этапе. Будьте предсказуемы позже.
И помните.
То, что код компилируется, не означает, что он безопасен.

