Перейти к содержимому

Лекция 7. Структуры и классы

1. Введение

Структуры (struct) и классы (class) — два основных способа определения пользовательских типов в Swift. В отличие от Python, где используется только class, Swift предлагает два механизма с принципиально разной семантикой: структуры — типы-значения (value types), а классы — ссылочные типы (reference types). Понимание этого различия — ключ к грамотному проектированию на Swift.


2. Синтаксис определения struct

Структура объявляется ключевым словом struct. Внутри определяются свойства и методы:

struct Point {
var x: Double
var y: Double
func distanceTo(_ other: Point) -> Double {
let dx = x - other.x
let dy = y - other.y
return (dx * dx + dy * dy).squareRoot()
}
}
let a = Point(x: 0, y: 0)
let b = Point(x: 3, y: 4)
print(a.distanceTo(b)) // 5.0

Для создания экземпляра используется memberwise initializer — автоматически сгенерированный инициализатор, принимающий все хранимые свойства. Такой инициализатор есть только у структур. В Python аналог __init__ нужно писать вручную.


3. Синтаксис определения class

Класс объявляется ключевым словом class. В отличие от структуры, класс не имеет автоматического memberwise initializer — необходимо определить init:

class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func introduce() -> String {
return "Меня зовут \(name), мне \(age) лет."
}
}
let person = Person(name: "Алексей", age: 22)
print(person.introduce()) // Меня зовут Алексей, мне 22 лет.

4. Хранимые свойства (Stored Properties)

Хранимые свойства — переменные (var) или константы (let), которые хранят значения внутри экземпляра:

struct Book {
let title: String // неизменяемое
var currentPage: Int // изменяемое
}
var book = Book(title: "Война и мир", currentPage: 0)
book.currentPage = 42
// book.title = "Другое" // Ошибка: title — let

Важно: если экземпляр структуры объявлен как let, изменять даже var-свойства нельзя. Для классов это правило не действует: let запрещает менять саму ссылку, но не свойства объекта.

let fixedBook = Book(title: "Swift", currentPage: 0)
// fixedBook.currentPage = 10 // Ошибка: fixedBook — let (struct)
let person = Person(name: "Ольга", age: 25)
person.age = 26 // OK: person — ссылка, свойство age — var

5. Вычисляемые свойства (Computed Properties)

Вычисляемые свойства не хранят значение, а вычисляют его при обращении:

struct Rectangle {
var width: Double
var height: Double
var area: Double {
get { return width * height }
set {
width = newValue.squareRoot()
height = newValue.squareRoot()
}
}
}
var rect = Rectangle(width: 5, height: 3)
print(rect.area) // 15.0
rect.area = 64
print(rect.width) // 8.0

5.1. Свойства только для чтения (Read-Only Computed)

Если set не нужен, можно опустить get:

struct Circle {
var radius: Double
var circumference: Double { return 2 * Double.pi * radius }
var areaValue: Double { return Double.pi * radius * radius }
}

В Python аналогом служит декоратор @property:

class Circle:
def __init__(self, radius):
self.radius = radius
@property
def circumference(self):
import math
return 2 * math.pi * self.radius

6. Наблюдатели свойств: willSet и didSet

Наблюдатели позволяют выполнять код до и после изменения хранимого свойства:

struct Progress {
var task: String
var amount: Int {
willSet { print("Было: \(amount), будет: \(newValue)") }
didSet {
if amount > 100 { amount = 100 }
print("Прогресс '\(task)': \(amount)%")
}
}
}
var p = Progress(task: "Загрузка", amount: 0)
p.amount = 50 // Было: 0, будет: 50 → Прогресс 'Загрузка': 50%
p.amount = 120 // Было: 50, будет: 120 → Прогресс 'Загрузка': 100%
  • willSet — вызывается перед изменением; newValue содержит новое значение.
  • didSet — вызывается после изменения; oldValue содержит предыдущее значение.

Наблюдатели не вызываются при инициализации — только при последующих присваиваниях.


7. Свойства типа (Static Properties)

Свойства типа принадлежат самому типу, а не экземпляру. Объявляются с static:

struct AppConfig {
static let version = "2.1.0"
static var requestCount = 0
}
print(AppConfig.version) // 2.1.0
AppConfig.requestCount += 1

Аналог в Python — атрибуты класса:

class AppConfig:
version = "2.1.0"
request_count = 0

8. Методы экземпляра и методы типа

Методы экземпляра вызываются на конкретном экземпляре. Методы типа (static / class) — на самом типе:

struct MathHelper {
static func factorial(_ n: Int) -> Int {
return n <= 1 ? 1 : n * factorial(n - 1)
}
}
print(MathHelper.factorial(5)) // 120

В классах class func (вместо static func) разрешает переопределение в подклассах (Лекция 8).


9. Ключевое слово mutating для методов struct

Методы структуры по умолчанию не могут изменять свойства. Для разрешения мутации используется mutating:

struct Location {
var latitude: Double
var longitude: Double
mutating func moveNorth(by degrees: Double) {
latitude += degrees
}
}
var loc = Location(latitude: 55.75, longitude: 37.62)
loc.moveNorth(by: 1.0)
print(loc.latitude) // 56.75

У классов mutating не нужен — классы являются ссылочными типами и могут изменять свойства свободно. В Python такого ограничения тоже нет.


10. Инициализаторы (init)

10.1. Memberwise initializer для struct

Структуры автоматически получают инициализатор со всеми хранимыми свойствами. Если вы добавите собственный init, автоматический пропадёт. Чтобы сохранить оба, определите кастомный init в расширении:

struct Size {
var width: Double
var height: Double
}
extension Size {
init(square side: Double) {
self.width = side
self.height = side
}
}
let a = Size(width: 10, height: 20) // memberwise — сохранился
let b = Size(square: 15) // кастомный

10.2. Кастомные инициализаторы для class

Класс требует init, инициализирующего все хранимые свойства:

class Student {
var name: String
var grade: Int
init(name: String, grade: Int) {
self.name = name
self.grade = grade
}
convenience init(name: String) {
self.init(name: name, grade: 1)
}
}
let s1 = Student(name: "Мария", grade: 3)
let s2 = Student(name: "Иван") // grade = 1

convenience init — вспомогательный инициализатор, который должен вызвать другой init того же класса.


11. Value Types vs Reference Types — ключевая тема

Это фундаментальное различие между struct и class в Swift.

Struct — тип-значение. При присваивании создаётся независимая копия:

struct Coordinate {
var x: Int
var y: Int
}
var point1 = Coordinate(x: 10, y: 20)
var point2 = point1 // КОПИЯ
point2.x = 99
print(point1.x) // 10 — оригинал не изменился
print(point2.x) // 99

Class — ссылочный тип. При присваивании копируется ссылка — оба имени указывают на один объект:

class CoordinateRef {
var x: Int
var y: Int
init(x: Int, y: Int) { self.x = x; self.y = y }
}
var ref1 = CoordinateRef(x: 10, y: 20)
var ref2 = ref1 // ССЫЛКА
ref2.x = 99
print(ref1.x) // 99 — оригинал изменился!
print(ref2.x) // 99

Запомните правило: struct — копия, class — ссылка. Это влияет на каждую строку кода, где вы передаёте или присваиваете значения.


12. Identity Operator === для классов

Оператор === проверяет, указывают ли две переменные на один и тот же объект в памяти:

let personA = Person(name: "Анна", age: 30)
let personB = personA
let personC = Person(name: "Анна", age: 30)
print(personA === personB) // true — одна ссылка
print(personA === personC) // false — разные объекты

== проверяет равенство значений (если тип реализует Equatable), а ===идентичность. Для структур === неприменим.


13. Сравнение с Python

В Python все объекты — ссылочные типы. Присваивание b = a копирует ссылку, и изменение b.x затрагивает a. Для создания копии нужен модуль copy:

a = Point(10, 20)
b = a # ссылка — b.x = 99 изменит и a.x
import copy
c = copy.copy(a) # копия — изменение c не затронет a

В Swift структура копируется автоматически — это делает код предсказуемым и безопасным.

АспектSwift structSwift classPython class
ТипValue typeReference typeReference type
ПрисваиваниеКопияСсылкаСсылка
Проверка идентичностиНеприменим===is
Изменение через копиюНе затрагивает оригиналЗатрагиваетЗатрагивает

14. Когда использовать struct, а когда class

Apple рекомендует по умолчанию использовать struct:

Используйте struct, когда:

  • Тип моделирует простые данные (координата, размер, цвет).
  • Нужна семантика копирования — каждый экземпляр независим.
  • Тип не требует наследования.

Используйте class, когда:

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

Большинство типов стандартной библиотеки Swift — структуры: Int, Double, String, Array, Dictionary, Set.


15. Сводная таблица: struct vs class

Характеристикаstructclass
ТипValue typeReference type
ПрисваиваниеКопированиеСсылка
Memberwise initАвтоматическийНет
НаследованиеНетДа
Деинициализатор (deinit)НетДа
mutating для методовДаНе нужен
Идентичность (===)НетДа
ПамятьСтек (обычно)Куча + ARC

16. Упражнения

Упражнение 1. Создайте структуру Temperature с хранимым свойством celsius: Double и вычисляемым свойством fahrenheitget и set). Формула: F = C × 9/5 + 32.

var temp = Temperature(celsius: 100)
print(temp.fahrenheit) // 212.0
temp.fahrenheit = 32
print(temp.celsius) // 0.0

Упражнение 2. Создайте структуру Vector2D с x, y: Double. Добавьте: вычисляемое свойство magnitude, mutating func scale(by:), статический метод static func add(_:_:) -> Vector2D.

Упражнение 3. Создайте класс Animal со свойствами name и sound. Присвойте один объект другому, измените свойство — убедитесь, что изменение отразилось на обоих. Повторите с аналогичной структурой и сравните.

Упражнение 4. Создайте структуру StepCounter со свойством totalSteps: Int с didSet, выводящим "Шагов добавлено: X. Всего: Y.".

var stepper = StepCounter(totalSteps: 0)
stepper.totalSteps = 100 // Шагов добавлено: 100. Всего: 100.
stepper.totalSteps = 360 // Шагов добавлено: 260. Всего: 360.

Упражнение 5. Создайте класс LinkedNode с var value: Int и var next: LinkedNode?. Постройте цепочку из трёх узлов и пройдите по ней. Объясните, почему для связного списка необходим class, а не struct.


17. Вопросы для самопроверки

  1. Чем отличается memberwise initializer от обычного init? У какого типа он генерируется автоматически?
  2. В чём разница между хранимыми и вычисляемыми свойствами?
  3. Когда вызываются willSet и didSet? Вызываются ли они при инициализации?
  4. Зачем нужно ключевое слово mutating для методов структуры?
  5. Что произойдёт, если структуру объявить как let и изменить её var-свойство?
  6. Чем отличается поведение присваивания для struct и class?
  7. Что проверяет оператор ===? Можно ли его применить к структуре?
  8. Какой оператор в Python аналогичен === в Swift?
  9. Почему Apple рекомендует по умолчанию использовать struct?
  10. Приведите три примера типов из стандартной библиотеки Swift, которые являются структурами.

18. Итоги

В этой лекции мы изучили:

  • Структуры и классы — два способа определения типов с различной семантикой.
  • Хранимые свойства (var, let) и вычисляемые свойства (get, set).
  • Наблюдатели свойств (willSet, didSet) для реакции на изменения.
  • Свойства и методы типа (static) — принадлежат типу, а не экземпляру.
  • Ключевое слово mutating — разрешение на изменение свойств в методах структуры.
  • Инициализаторы — memberwise для struct, обязательный init для class.
  • Value types vs Reference types — struct копируется, class передаётся по ссылке.
  • Оператор === — проверка идентичности объектов класса.
  • Рекомендации Apple — struct по умолчанию, class при необходимости наследования или идентичности.

В следующей лекции мы подробно рассмотрим наследование и полиморфизм — возможности, доступные только классам.