Топ ошибок начинающих Swift разработчиков: продвинутый уровень

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

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

Начать курс

Топ ошибок начинающих Swift разработчиков: продвинутый уровень

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

1. Утечки памяти в замыканиях: не только [weak self]

Самой популярной ошибкой является использование [weak self] без понимания того, когда это действительно нужно. Многие разработчики ставят weak self везде, где есть замыкание, что приводит к преждевременному уничтожению объектов. Или, наоборот, забывают про сильные ссылочные циклы в неочевидных местах.

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

class NetworkManager {
    var onCompletion: (() -> Void)?
    
    func fetchData() {
        // ОШИБКА: замыкание захватывает self сильно
        onCompletion = {
            self.updateUI()
        }
    }
    
    func updateUI() {
        print("UI обновлен")
    }
}

Правильное решение: использовать [weak self] или [unowned self] в зависимости от времени жизни объектов.

class NetworkManager {
    var onCompletion: (() -> Void)?
    
    func fetchData() {
        onCompletion = { [weak self] in
            guard let self = self else { return }
            self.updateUI()
        }
    }
    
    func updateUI() {
        print("UI обновлен")
    }
}

Продвинутый нюанс: будьте осторожны с [unowned self]. Если объект будет освобожден, а замыкание вызовется, приложение упадет с крашем. Используйте unowned только когда вы на 100% уверены, что объект переживет замыкание.

2. Неправильная работа с async/await и акторами

С появлением Structured Concurrency в Swift 5.5 многие разработчики начали активно использовать async/await, но забывают про MainActor и Sendable. Это приводит к race conditions и обновлению UI из фонового потока.

Типичная ошибка: вызов асинхронной функции, которая должна выполняться на главном потоке, без явного указания @MainActor.

class ViewModel: ObservableObject {
    @Published var data: [String] = []
    
    // ОШИБКА: функция не помечена @MainActor
    func loadData() async {
        let result = await fetchFromNetwork()
        // Это может выполниться на фоновом потоке!
        self.data = result
    }
    
    func fetchFromNetwork() async -> [String] {
        try? await Task.sleep(nanoseconds: 1_000_000_000)
        return ["Item1", "Item2"]
    }
}

Правильное решение: использовать @MainActor для функций, которые обновляют UI.

class ViewModel: ObservableObject {
    @Published var data: [String] = []
    
    @MainActor // Теперь гарантированно выполняется на главном потоке
    func loadData() async {
        let result = await fetchFromNetwork()
        self.data = result
    }
    
    func fetchFromNetwork() async -> [String] {
        try? await Task.sleep(nanoseconds: 1_000_000_000)
        return ["Item1", "Item2"]
    }
}

Продвинутый нюанс: не забывайте про nonisolated и actor. Если вы используете акторы, все публичные методы по умолчанию асинхронны. Это может привести к неожиданным задержкам, если вы не продумаете изоляцию.

3. Race Conditions в Combine и SwiftUI

Combine — мощный инструмент, но он легко порождает race conditions, когда несколько подписчиков одновременно изменяют одно и то же состояние. Особенно это критично в SwiftUI, где изменения @Published свойств триггерят перерисовку.

Типичная ошибка: подписка на издателя внутри onAppear без отмены подписки в onDisappear.

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        Text("Count: \(viewModel.count)")
            .onAppear {
                // ОШИБКА: новая подписка каждый раз без отмены старой
                Timer.publish(every: 1, on: .main, in: .common)
                    .autoconnect()
                    .sink { _ in
                        viewModel.count += 1
                    }
            }
    }
}

class ViewModel: ObservableObject {
    @Published var count = 0
}

Правильное решение: использовать onReceive или хранить AnyCancellable с очисткой.

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    @State private var cancellable: AnyCancellable?
    
    var body: some View {
        Text("Count: \(viewModel.count)")
            .onAppear {
                cancellable = Timer.publish(every: 1, on: .main, in: .common)
                    .autoconnect()
                    .sink { _ in
                        viewModel.count += 1
                    }
            }
            .onDisappear {
                cancellable?.cancel()
            }
    }
}

Продвинутый нюанс: если вы используете @Published в классе, который не является ObservableObject, Combine не будет работать корректно. Всегда проверяйте, что ваш класс наследуется от ObservableObject.

4. Ошибки с Generics и Protocol Associated Types

Generics в Swift — мощный инструмент, но их неправильное использование может привести к падению производительности или невозможности компиляции. Самая частая ошибка — использование Any вместо конкретного типа или неправильная работа с PAT (Protocol with Associated Types).

Типичная ошибка: попытка использовать протокол с associated type в качестве типа массива.

protocol DataProvider {
    associatedtype DataType
    func fetch() -> DataType
}

// ОШИБКА: протокол с associated type нельзя использовать как тип
var providers: [DataProvider] = [] // Ошибка компиляции

Правильное решение: использовать type erasure (стирание типа) или явно указывать тип.


                
                

Блоги

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