Avatar ·

Почему стоит выбирать структуру, а не класс в Swift?

Всем привет! Я только начал изучать Swift, и у меня возникла путаница. Я понимаю, что и структуры (struct), и классы (class) могут иметь свойства и методы. Вроде бы разница только в том, что структуры — это значимые типы (value types), а классы — ссылочные (reference types). Но на практике я не до конца понимаю, когда что применять. Вот пример. Я пишу простое приложение для заметок. Создал класс Note: ```swift class Note { var title: String var content: String init(title: String, content: String) { self.title = title self.content = content } } var note1 = Note(title: "Покупки", content: "Купить молоко") var note2 = note1 note2.title = "Срочные дела" print(note1.title) // Выведет "Срочные дела" — я не этого ожидал! ``` Я думал, что создаю копию, а оказалось, что обе переменные ссылаются на один и тот же объект. Из-за этого данные меняются в неожиданных местах. Если я заменю `class` на `struct`, то копирование будет работать как надо, и `note1.title` останется "Покупки". Но я встречал много примеров, где используют классы: UIKit (UIViewController, UIView), Core Data. Везде пишут, что классы нужны для наследования, а структуры — для простых данных. Но тогда почему Apple в своих фреймворках использует классы? Может, я что-то упускаю? Подскажите, пожалуйста, когда в Swift действительно нужно выбирать struct, а когда class? Есть ли какие-то чёткие правила или best practices? <<>>

Основное правило: «По умолчанию используйте структуры. Классы — только когда они действительно нужны»

Apple рекомендует такой подход в документации и WWDC-сессиях. Структуры безопаснее, быстрее и предсказуемее. Давайте разберёмся, почему.

1. Безопасность копирования (value semantics)

Структуры — значимые типы. Когда вы присваиваете структуру новой переменной или передаёте её в функцию, создаётся полная независимая копия. Это предотвращает случайное изменение данных:

struct Note {
    var title: String
    var content: String
}

var note1 = Note(title: "Покупки", content: "Купить молоко")
var note2 = note1
note2.title = "Срочные дела"
print(note1.title) // "Покупки" — оригинал не изменился!

С классами такого не происходит — они ссылочные типы. Все переменные указывают на один объект в памяти. Одна мутация — и все «владельцы» получают изменения. Это частая причина багов, особенно при работе с многопоточностью.

2. Наследование — не всегда плюс

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

Пример: вместо класса-предка Shape и его наследников, вы можете сделать протокол Shape и несколько структур, которые его реализуют:

protocol Shape {
    var area: Double { get }
}

struct Circle: Shape {
    let radius: Double
    var area: Double { .pi * radius * radius }
}

struct Square: Shape {
    let side: Double
    var area: Double { side * side }
}

3. Производительность и управление памятью

Структуры хранятся в стеке (кроме случаев, когда они являются частью класса). Классы — в куче (heap). Работа со стеком:

  • Быстрее (нет поиска свободного блока памяти)
  • Не требует подсчёта ссылок (ARC)
  • Не создаёт лишней нагрузки на сборщик мусора

Для небольших объектов (до нескольких свойств) структуры значительно эффективнее. Классы с ARC могут вызывать задержки при частом создании/удалении.

4. Когда выбирать класс?

Есть три ситуации, где классы незаменимы:

  • Наследование от системных классов (UIViewController, UIView, NSObject). Это требование UIKit/AppKit.
  • Необходимость идентификации (identity). Если два объекта должны считаться равными, только если это один и тот же экземпляр (например, пользовательская сессия).
  • Совместное владение данными (shared mutable state). Когда один объект должен быть изменён из разных частей приложения (например, синглтон или кэш).

5. Практический чек-лист

Задайте себе вопросы:

  • Нужно ли мне наследование от UIKit/Objective-C? → class
  • Должны ли все копии быть независимыми? → struct
  • Требуется ли идентификация (===)? → class
  • Объект небольшой (до 3-5 свойств)? → struct
  • Данные часто копируются? → struct
  • Нужно ли изменять объект из нескольких мест одновременно? → class

В вашем примере с заметками struct Note — идеальный выбор. Данные не должны мутироваться неожиданно. А вот ViewController, который управляет экраном, должен быть классом — он наследуется от UIViewController.

<<>>

Дополнение: «Лучше начать со структуры и перейти к классу, если понадобится»

На практике я часто вижу обратную ситуацию: разработчики по привычке пишут class для всего, а потом удивляются неожиданным мутациям. Мой совет — начинайте со struct. Если позже выяснится, что нужна идентификация или наследование, переделать структуру в класс — это пара секунд. А вот наоборот — класс в структуру — может быть больно, если код уже использует ссылочную семантику.

Ещё один важный момент: не злоупотребляйте мутабельностью. В Swift есть ключевое слово mutating для методов структур, которые изменяют свойства. Если вы пишете struct, старайтесь делать свойства иммутабельными (let), а для изменений возвращать новую копию. Это делает код чище и безопаснее:

struct Note {
    let title: String
    let content: String
    
    func withUpdatedTitle(_ newTitle: String) -> Note {
        Note(title: newTitle, content: self.content)
    }
}

var note = Note(title: "Покупки", content: "Молоко")
note = note.withUpdatedTitle("Список дел") // явное обновление

Такой подход особенно полезен в многопоточных средах — вы гарантированно не получите состояние гонки (race condition), потому что данные никогда не меняются «на месте».