Топ ошибок начинающих Swift разработчиков: от А до Я

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

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

Начать курс

Топ ошибок начинающих Swift разработчиков: от А до Я

Swift — современный, мощный и интуитивно понятный язык, созданный Apple для разработки под iOS, macOS, watchOS и tvOS. Однако его уникальные фишки (опциональные типы, строгая типизация, ARC и протоколы) часто становятся камнем преткновения для новичков. В этой статье мы разберем самые частые ошибки начинающих Swift-разработчиков — от "А" (Optionals) до "Я" (ARC). Вы узнаете, как их избежать и писать чистый, безопасный код.

1. Неправильная работа с Optionals (Необязательными типами)

Самая распространенная ошибка — игнорирование концепции nil и опциональных типов. Новички часто пытаются использовать значение, не убедившись, что оно существует.

Ошибка: Принудительное извлечение (Force Unwrapping)

let optionalString: String? = nil
let unwrapped = optionalString! // Crash! Fatal error

Правильное решение: Безопасное извлечение

if let safeString = optionalString {
    print(safeString)
} else {
    print("Значение отсутствует")
}

// Или с помощью guard
guard let safeString = optionalString else {
    print("Завершаем выполнение")
    return
}
print(safeString)

Ошибка: Использование implicitly unwrapped optionals без гарантии значения

var name: String! = nil
print(name.count) // Crash

Совет: Используйте ? для объявления опционалов и всегда проверяйте значение перед использованием. Избегайте ! в объявлениях, если не уверены на 100%, что значение будет установлено до первого обращения.

2. Ошибки управления памятью (ARC) и циклы сильных ссылок

Swift использует Automatic Reference Counting (ARC) для управления памятью. Главная ошибка новичков — создание циклов сильных ссылок (retain cycles), которые приводят к утечкам памяти.

Проблема: Замыкания, захватывающие self

class ViewController: UIViewController {
    var closure: (() -> Void)?

    func setupClosure() {
        closure = {
            self.view.backgroundColor = .red // Сильная ссылка на self
        }
    }
}

Так как closure хранит сильную ссылку на self, а self (через свойство closure) хранит сильную ссылку на замыкание, образуется цикл.

Решение: Списки захвата (Capture Lists)

closure = { [weak self] in
    guard let self = self else { return }
    self.view.backgroundColor = .red
}

// Или unowned, если уверены, что self не станет nil
closure = { [unowned self] in
    self.view.backgroundColor = .red
}

Проблема: Циклы между объектами

class Parent {
    var child: Child?
}

class Child {
    var parent: Parent? // Сильная ссылка на Parent
}

Решение: Слабая ссылка у одной из сторон

class Child {
    weak var parent: Parent? // Теперь это слабая ссылка
}

3. Игнорирование Value Semantics (Семантики значений) против Reference Semantics

Новички часто путают классы (ссылочные типы) и структуры (типы-значения). Это приводит к неожиданному поведению, особенно при работе с коллекциями.

Ошибка: Изменение экземпляра класса внутри коллекции

class User {
    var name: String
    init(name: String) { self.name = name }
}

var users = [User(name: "Alice")]
let firstUser = users[0]
firstUser.name = "Bob" // Изменение внутри коллекции
print(users[0].name) // "Bob" - коллекция изменилась

Это может быть как плюсом, так и источником багов, если разработчик ожидал копирования.

Правильный подход: Использовать структуры для данных

struct User {
    var name: String
}

var users = [User(name: "Alice")]
var firstUser = users[0] // Это копия
firstUser.name = "Bob"   // Изменяем копию
print(users[0].name)     // "Alice" - оригинал не изменился

Совет: Используйте struct по умолчанию для моделей данных. Классы (class) используйте, когда нужна идентичность (например, для UIViewController) или наследование.

4. Непонимание работы с протоколами и делегированием

Делегирование — ключевой паттерн в Cocoa Touch, но новички часто делают его неправильно, что приводит к "молчаливым" ошибкам.

Ошибка: Делегат не помечен как weak

protocol MyDelegate: AnyObject {
    func didSomething()
}

class Sender {
    var delegate: MyDelegate? // Сильная ссылка! Цикл!
}

class Receiver: MyDelegate {
    let sender = Sender()
    init() {
        sender.delegate = self // Retain cycle
    }
    func didSomething() { }
}

Правильное решение: Слабая ссылка на делегата

class Sender {
    weak var delegate: MyDelegate? // Слабая ссылка
}

Не забывайте наследовать протокол от AnyObject, чтобы компилятор разрешил использовать weak.

Ошибка: Непроверка существования делегата

delegate?.didSomething() // Правильно: опциональная цепочка
// delegate!.didSomething() // Неправильно: краш, если delegate == nil

5. Ошибки при работе с UI и потоками (Main Thread Checker)

Все обновления UI должны происходить на главном потоке. Нарушение этого правила — верный способ получить нестабильное приложение.

Ошибка: Обновление UI из фонового потока

DispatchQueue.global().async {
    // Тяжелые вычисления
    self.label.text = "Результат" // Ошибка!
}

Правильное решение: DispatchQueue.main.async

DispatchQueue.global().async {
    // Тяжелые вычисления
    let result = "Результат"
    DispatchQueue.main.async {
        self.label.text = result
    }
}

// Современный способ (iOS 13+)
Task {
    let result = await heavyComputation()
    await MainActor.run {
        self.label.text = result
    }
}

Заключение

Блоги

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