Site icon AppTractor

10 ловушек Swift, которые вы не заметите, пока не станет слишком поздно

Вы не узнаете об этом из курса или блогов. Эти знания получены из реальных ошибок в продакшене, работы по ночам и того одного сбоя, который мы не могли воспроизвести в течение нескольких недель.

Вот 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 — мощный язык, но он не является безошибочным.

Компилятор помогает, но везде есть скрытые ловушки. Большинство из этих ловушек не приведут к сбою в первый день — они проявятся через несколько недель, когда ваше приложение начнет вести себя странно или излишне потреблять память.

Будьте параноиком на раннем этапе. Будьте предсказуемы позже.

И помните.

То, что код компилируется, не означает, что он безопасен.

Источник

Exit mobile version