Struct vs Class в Swift: когда и почему выбирать структуру

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

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

Начать курс

Struct или Class? Вечный вопрос Swift-разработчика

Каждый, кто начинает писать на Swift, рано или поздно сталкивается с этим вопросом. Казалось бы, обе конструкции позволяют создавать объекты с данными и методами. Но почему же на Stack Overflow этот вопрос набирает тысячи просмотров, а опытные разработчики всё чаще отдают предпочтение структурам?

Ответ прост: потому что выбор между struct и class напрямую влияет на производительность, потокобезопасность и архитектуру вашего приложения. В этой статье мы разберём не просто теорию, а реальные примеры и сценарии, когда Struct однозначно побеждает Class.

Если вы пишете на Swift и до сих пор используете классы «по привычке» из Objective-C — эта статья для вас. Готовьтесь переосмыслить свои подходы.

Главное отличие: Value Type vs Reference Type

Прежде чем углубляться в детали, давайте вспомним фундаментальное различие:

  • Struct — это value type (тип-значение). При присваивании или передаче в функцию создаётся копия данных.
  • Class — это reference type (ссылочный тип). При присваивании или передаче в функцию передаётся ссылка на один и тот же объект в памяти.

Казалось бы, мелочь. Но из этого «атомного» различия вытекают все последствия, которые мы сейчас разберём.

Пример 1: Неожиданное поведение Class

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

struct PersonStruct {
    var name: String
}

// Тестируем класс
let person1 = PersonClass(name: "Анна")
let person2 = person1
person2.name = "Мария"

print(person1.name) // Мария! Изменилось и у person1
print(person2.name) // Мария

Заметили проблему? Изменив имя у person2, мы невольно изменили его и у person1. Ведь это один и тот же объект в памяти. В маленьком примере это не критично, но в реальном проекте такое «побочное изменение» может привести к трудноотловимым багам.

// Тестируем структуру
var person3 = PersonStruct(name: "Анна")
var person4 = person3
person4.name = "Мария"

print(person3.name) // Анна! Всё на своих местах
print(person4.name) // Мария

Структура ведёт себя предсказуемо: каждая переменная владеет своей независимой копией данных. Это безопасно и прозрачно.

Когда Struct — очевидный выбор?

Apple в своей документации рекомендует использовать struct по умолчанию. Давайте перечислим конкретные сценарии, где структура выигрывает без вариантов.

1. Данные, которые должны быть неизменяемыми

Если вы работаете с моделями, которые не должны меняться после создания (например, результаты запроса к серверу), struct с константными свойствами — идеальный выбор.

struct UserProfile {
    let id: Int
    let name: String
    let email: String
}

// Создали — и забыли. Данные защищены от случайных изменений.
let profile = UserProfile(id: 1, name: "Иван", email: "ivan@example.com")

С классом вы бы всё равно могли по ошибке изменить name, если бы свойство было объявлено как var. Со структурой компилятор заставит вас думать о неизменности.

2. Небольшие, самодостаточные сущности

Если ваша модель описывает что-то простое — координаты, размеры, цвет, денежную сумму — используйте struct. Это логично и эффективно.

struct Point {
    var x: Double
    var y: Double
}

struct Money {
    var amount: Double
    var currency: String
}

struct Rectangle {
    var origin: Point
    var size: Size
}

Такие типы не нуждаются в наследовании и управлении ссылками. Они просто носят данные.

3. Когда важна потокобезопасность

В многопоточных приложениях классы — источник головной боли. Два потока могут одновременно обратиться к одному объекту и «перетереть» данные друг друга. Со структурами каждый поток получает свою копию — никаких гонок данных.

// Класс — легко создать баг в многопоточке
class SharedCounter {
    var count = 0
}

// Структура — каждый поток работает с копией
struct SafeCounter {
    var count = 0
}

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

Когда Class всё-таки нужен?

Было бы несправедливо сказать, что классы — это «зло». У них есть свои незаменимые сценарии:

  • Наследование. Вам нужна иерархия классов (например, базовый класс Animal → Dog, Cat).
  • Ссылочная семантика. Вам нужно, чтобы несколько частей приложения «видели» один и тот же объект (например, синглтон или общий менеджер состояния).
  • Работа с Objective-C. Некоторые фреймворки требуют классы.
  • Deinit. Вам нужен деструктор для освобождения ресурсов.

Но даже в этих случаях подумайте: может, можно обойтись композицией структур вместо наследования классов? Современный Swift всё чаще склоняется к протоколам и структурам, а не к классовой иерархии.

Производительность: Struct быстрее?

Короткий ответ: да, обычно быстрее. Вот почему:

  • Память на стеке. Структуры хранятся в стеке (если не являются частью класса). Работа со стеком гораздо быстрее, чем с кучей (heap), где живут классы.
  • Нет подсчёта ссылок. Классы требуют ARC (Automatic Reference Counting) — каждый раз при присваивании или удалении ссылки счётчик меняется. Это оверхед.
  • Локализация данных. Структуры лежат ближе друг к другу в памяти, что улучшает кэширование процессора.
// Пример из реального проекта: массив из 100 000 структур vs классов
struct SmallStruct {
    let x: Int
    let y: Int
}

class SmallClass {
    let x: Int
    let y: Int
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

// Создание массива структур — быстрее, меньше аллокаций в куче
var structArray = Array(repeating: SmallStruct(x: 0, y: 0), count: 100_000)

// Создание массива классов — 100 000 отдельных объектов в куче
var classArray = (0..<100_000).map { _ in SmallClass(x: 0, y: 0) }

В бенчмарках разница может достигать

Блоги

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