Оптимизация производительности Swift приложений для начинающих

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

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

Начать курс

Введение: почему производительность важна с первого дня

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

В этой статье мы разберём ключевые аспекты оптимизации Swift-приложений для iOS и macOS. Вы узнаете, как избежать типичных ошибок новичков, научитесь работать с памятью, потоками и профилированием. Каждый раздел содержит практические примеры кода, которые вы можете сразу применить в своих проектах.

1. Управление памятью: ARC и избегание retain cycles

Swift использует Automatic Reference Counting (ARC) для управления памятью. Это значит, что объекты освобождаются, когда на них больше нет сильных ссылок. Но если вы создадите циклическую ссылку (retain cycle), память никогда не освободится — произойдёт утечка.

Слабая и бесхозная ссылки

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

class ViewController: UIViewController {
    var networkManager: NetworkManager?
    
    func fetchData() {
        networkManager?.fetch { [weak self] result in
            guard let self = self else { return }
            // Обработка результата
            self.updateUI(with: result)
        }
    }
}

Когда использовать:

  • weak — если ссылка может стать nil (например, делегат).
  • unowned — если вы уверены, что объект будет жить дольше замыкания (например, родительский контроллер).

Избегайте ненужных копий

Value types (структуры, перечисления) копируются при присваивании. Для больших данных используйте class или передавайте по ссылке через inout.

// Плохо: копирование большого массива
func processLargeArray(_ array: [Int]) {
    // array — копия
}

// Хорошо: передача по ссылке
func processLargeArray(_ array: inout [Int]) {
    // array — ссылка на оригинал
}

2. Работа с многопоточностью: GCD и async/await

iOS и macOS — многопоточные системы. Если вы выполняете тяжёлые операции (загрузка данных, обработка изображений) на главном потоке, интерфейс "замерзает". Используйте Grand Central Dispatch (GCD) или современный async/await.

GCD: базовые паттерны

DispatchQueue.global(qos: .userInitiated).async {
    // Тяжёлая работа в фоне
    let result = performHeavyCalculation()
    
    DispatchQueue.main.async {
        // Обновление UI на главном потоке
        self.label.text = "\(result)"
    }
}

QoS (Quality of Service):

  • .userInteractive — для UI (почти мгновенно).
  • .userInitiated — для действий пользователя (секунды).
  • .utility — для длительных задач (прогресс-бары).
  • .background — для фоновой синхронизации (не срочно).

Async/await (Swift 5.5+)

Более читаемый способ работы с асинхронностью:

func fetchImage(from url: URL) async throws -> UIImage {
    let (data, _) = try await URLSession.shared.data(from: url)
    guard let image = UIImage(data: data) else {
        throw URLError(.badServerResponse)
    }
    return image
}

// Вызов в Task
Task {
    do {
        let image = try await fetchImage(from: url)
        self.imageView.image = image
    } catch {
        print("Ошибка загрузки: \(error)")
    }
}

Совет: Не вызывайте Task внутри viewDidLoad без сохранения ссылки — задача может быть отменена при уходе с экрана.

3. Оптимизация UI: ленивая загрузка и повторное использование

Пользовательский интерфейс — самое дорогое место в приложении. Каждый лишний UIView или CALayer замедляет рендеринг.

Ленивая загрузка (Lazy Loading)

Создавайте объекты только тогда, когда они нужны:

class ProfileViewController: UIViewController {
    lazy var profileImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        return imageView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(profileImageView) // Создаётся только здесь
    }
}

Повторное использование ячеек (UITableView/UICollectionView)

Никогда не создавайте новые ячейки вручную. Используйте dequeueReusableCell:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCustomCell
    cell.configure(with: data[indexPath.row])
    return cell
}

Дополнительно:

  • Избегайте cornerRadius на каждой ячейке — используйте maskToBounds только при необходимости.
  • Не переопределяйте layoutSubviews без крайней нужды — это вызывается часто.

4. Профилирование: как найти узкие места

Без измерений оптимизация — это гадание. Xcode предоставляет мощный инструмент Instruments.

Базовые шаги профилирования

  1. Time Profiler — показывает, какие функции потребляют больше всего времени.
  2. Allocations — отслеживает утечки памяти и количество создаваемых объектов.
  3. Energy Log — для iOS: что разряжает батарею (сеть, GPS, фоновые задачи).

Блоги

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