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) }
В бенчмарках разница может достигать
Блоги
Рекомендаций по книгам
Читайте также