Лекция 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 — var5. Вычисляемые свойства (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.0rect.area = 64print(rect.width) // 8.05.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.radius6. Наблюдатели свойств: 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.0AppConfig.requestCount += 1Аналог в Python — атрибуты класса:
class AppConfig: version = "2.1.0" request_count = 08. Методы экземпляра и методы типа
Методы экземпляра вызываются на конкретном экземпляре. Методы типа (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 = 1convenience 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) // 99Class — ссылочный тип. При присваивании копируется ссылка — оба имени указывают на один объект:
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 = personAlet 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.ximport copyc = copy.copy(a) # копия — изменение c не затронет aВ Swift структура копируется автоматически — это делает код предсказуемым и безопасным.
| Аспект | Swift struct | Swift class | Python class |
|---|---|---|---|
| Тип | Value type | Reference type | Reference type |
| Присваивание | Копия | Ссылка | Ссылка |
| Проверка идентичности | Неприменим | === | is |
| Изменение через копию | Не затрагивает оригинал | Затрагивает | Затрагивает |
14. Когда использовать struct, а когда class
Apple рекомендует по умолчанию использовать struct:
Используйте struct, когда:
- Тип моделирует простые данные (координата, размер, цвет).
- Нужна семантика копирования — каждый экземпляр независим.
- Тип не требует наследования.
Используйте class, когда:
- Нужно наследование.
- Нужна идентичность — важно, чтобы переменные указывали на один объект.
- Объект управляет внешним ресурсом (файл, соединение).
Большинство типов стандартной библиотеки Swift — структуры:
Int,Double,String,Array,Dictionary,Set.
15. Сводная таблица: struct vs class
| Характеристика | struct | class |
|---|---|---|
| Тип | Value type | Reference type |
| Присваивание | Копирование | Ссылка |
| Memberwise init | Автоматический | Нет |
| Наследование | Нет | Да |
Деинициализатор (deinit) | Нет | Да |
mutating для методов | Да | Не нужен |
Идентичность (===) | Нет | Да |
| Память | Стек (обычно) | Куча + ARC |
16. Упражнения
Упражнение 1. Создайте структуру Temperature с хранимым свойством celsius: Double и вычисляемым свойством fahrenheit (с get и set). Формула: F = C × 9/5 + 32.
var temp = Temperature(celsius: 100)print(temp.fahrenheit) // 212.0temp.fahrenheit = 32print(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. Вопросы для самопроверки
- Чем отличается memberwise initializer от обычного
init? У какого типа он генерируется автоматически? - В чём разница между хранимыми и вычисляемыми свойствами?
- Когда вызываются
willSetиdidSet? Вызываются ли они при инициализации? - Зачем нужно ключевое слово
mutatingдля методов структуры? - Что произойдёт, если структуру объявить как
letи изменить еёvar-свойство? - Чем отличается поведение присваивания для struct и class?
- Что проверяет оператор
===? Можно ли его применить к структуре? - Какой оператор в Python аналогичен
===в Swift? - Почему Apple рекомендует по умолчанию использовать struct?
- Приведите три примера типов из стандартной библиотеки Swift, которые являются структурами.
18. Итоги
В этой лекции мы изучили:
- Структуры и классы — два способа определения типов с различной семантикой.
- Хранимые свойства (
var,let) и вычисляемые свойства (get,set). - Наблюдатели свойств (
willSet,didSet) для реакции на изменения. - Свойства и методы типа (
static) — принадлежат типу, а не экземпляру. - Ключевое слово
mutating— разрешение на изменение свойств в методах структуры. - Инициализаторы — memberwise для struct, обязательный
initдля class. - Value types vs Reference types — struct копируется, class передаётся по ссылке.
- Оператор
===— проверка идентичности объектов класса. - Рекомендации Apple — struct по умолчанию, class при необходимости наследования или идентичности.
В следующей лекции мы подробно рассмотрим наследование и полиморфизм — возможности, доступные только классам.