В этом уроке мы сделаем модель товара через struct, напишем функцию расчета скидки, добавим метод и отдельно посмотрим, чем class отличается от struct на простом примере. Это важный мост от первых переменных к реальным Swift-моделям
К концу урока вы поймете, почему Swift часто начинает с struct, когда нужен тип данных, и почему class выбирают не «потому что так в ООП», а когда нужна ссылочная семантика
- Что получится в конце
- Функция в Swift
- Struct: тип данных со свойствами
- Метод внутри struct
- Value semantics
- Class: ссылочная семантика
- Домашка: корзина товаров
- Когда выбирать struct, а когда class
- Частые ошибки и порядок проверки
- Argument labels: почему вызов функции читается как фраза
- Computed properties в struct
- mutating method: когда struct меняет себя
- Class и identity на пальцах
- Что может быть еще интересно по этой теме
- Что почитать дальше по Swift
Что получится в конце
Код:
struct Product {
let title: String
let price: Int
let category: String
func label() -> String {
"\(title): \(price)"
}
}
func discounted(_ product: Product, percent: Int) -> Product {
let safePercent = min(max(percent, 0), 90)
let discount = product.price * safePercent / 100
return Product(
title: product.title,
price: product.price - discount,
category: product.category
)
}
let keyboard = Product(title: "Keyboard", price: 8000, category: "hardware")
let saleKeyboard = discounted(keyboard, percent: 15)
print(keyboard.label())
print(saleKeyboard.label())
Ожидаемый смысл: исходный товар стоит 8000, версия со скидкой — 6800
Функция в Swift
Функция:
func discounted(_ product: Product, percent: Int) -> Product
принимает товар и процент, возвращает новый товар
В Swift есть внешние и внутренние имена параметров. В нашем примере _ product отключает внешнее имя для первого параметра:
discounted(keyboard, percent: 15)
А percent остается именованным. Это делает вызов похожим на фразу: discounted keyboard, percent 15
Если функция короткая, можно писать компактнее, но для учебного материала лучше оставить несколько шагов явно: безопасный процент, скидка, новый объект
Struct: тип данных со свойствами
struct собирает связанные значения:
struct Product {
let title: String
let price: Int
let category: String
}
Теперь товар — не три отдельные переменные, а один тип. Это делает код понятнее: функции принимают Product, массивы хранят [Product], UI показывает поля product.title и product.price
Swift автоматически дает memberwise initializer:
let keyboard = Product(title: "Keyboard", price: 8000, category: "hardware")
Для первых моделей это удобно: не нужно писать init вручную
Метод внутри struct
Метод:
func label() -> String {
"\(title): \(price)"
}
живет внутри Product и работает с его свойствами. Вызов:
keyboard.label()
Если операция естественно относится к товару, метод читается хорошо. Если операция объединяет много разных типов или внешних зависимостей, отдельная функция может быть честнее
Value semantics
Struct в Swift обычно имеет value semantics. Когда вы присваиваете значение другой переменной, вы работаете с отдельным значением, а не с общей ссылкой в привычном смысле
Пример:
var first = Product(title: "Mouse", price: 3000, category: "hardware")
var second = first
second = Product(title: "Mouse", price: 2500, category: "hardware")
print(first.price)
print(second.price)
Изменение second не должно неожиданно менять first. Для моделей данных это часто очень удобно
Class: ссылочная семантика
Class ведет себя иначе:
class Counter {
var value = 0
}
let first = Counter()
let second = first
second.value += 1
print(first.value)
print(second.value)
Оба имени смотрят на один объект. Поэтому изменение через second видно через first
Это не плохо. Class нужен, когда требуется identity, shared mutable state, наследование или работа с фреймворками, где классы являются основой. Но для простых моделей данных в Swift часто начинают со struct
Домашка: корзина товаров
Создайте:
struct CartItem {
let title: String
let price: Int
let quantity: Int
}
Добавьте метод:
func total() -> Int
Он должен возвращать price * quantity. Создайте два товара и выведите:
Keyboard x1 = 8000
Mouse x2 = 7000
Дополнительная задача: напишите функцию, которая принимает [CartItem] и возвращает сумму корзины
Когда выбирать struct, а когда class
Выбирайте struct, если это модель данных: товар, пользователь, задача, настройка, ответ API. Особенно если значение удобно копировать и передавать без общей изменяемой ссылки
Выбирайте class, если нужна идентичность объекта, наследование, shared state или конкретный Apple framework требует class
Для новичка практичная привычка: начать со struct, перейти на class только когда появилась причина
Частые ошибки и порядок проверки
Путаете функцию и метод Функция вызывается отдельно, метод вызывается у значения через точку
Ждете от struct поведения class Struct не дает общей ссылочной семантики как class. Это часто плюс, а не минус
Пишете class для каждой модели В Swift модели данных часто удобнее делать struct
Забыли return Если тело функции не expression-style, верните значение явно через return
Argument labels: почему вызов функции читается как фраза
В Swift важна читаемость вызова. Поэтому параметры могут иметь внешние имена:
func discountPrice(for price: Int, percent: Int) -> Int {
price - price * percent / 100
}
let result = discountPrice(for: 1500, percent: 10)
Вызов discountPrice(for:percent:) читается почти как фраза. Это отличается от языков, где важен только порядок аргументов. Для маленьких примеров кажется непривычным, но в больших API сильно помогает
Если внешнее имя мешает, его можно убрать через _:
func printTitle(_ title: String) {
print(title)
}
printTitle("Swift")
Не злоупотребляйте _. Если функция принимает несколько значений одного типа, внешние имена защищают от путаницы:
func move(from start: Int, to end: Int) {
print("Move from \(start) to \(end)")
}
Computed properties в struct
struct может хранить свойства и вычислять значения:
struct Product {
let title: String
let price: Int
let discountPercent: Int
var finalPrice: Int {
price - price * discountPercent / 100
}
}
let course = Product(title: "Swift course", price: 2000, discountPercent: 15)
print(course.finalPrice)
finalPrice не хранится отдельно. Он вычисляется из price и discountPercent. Это снижает риск рассинхронизации: не получится случайно поменять цену и забыть обновить итог
mutating method: когда struct меняет себя
Если метод struct меняет свойства, он должен быть помечен как mutating:
struct Counter {
var value = 0
mutating func increment() {
value += 1
}
}
var counter = Counter()
counter.increment()
print(counter.value)
Обратите внимание на var counter. Если написать let counter, вызвать increment() нельзя, потому что константное значение struct менять запрещено
Это еще одна причина начинать с let: Swift сразу показывает, где изменение действительно нужно
Class и identity на пальцах
У class важна identity: две переменные могут ссылаться на один и тот же объект
final class Player {
var score = 0
}
let first = Player()
let second = first
second.score = 10
print(first.score)
Вывод будет:
10
first и second смотрят на один объект. Это удобно для общих mutable-состояний, но требует дисциплины. Изменение в одном месте может проявиться в другом, и это не всегда очевидно при чтении кода
Для учебных моделей вроде товара, задачи или пользователя чаще начинайте со struct. Переходите на class, когда вам действительно нужна identity, наследование или совместимость с API, построенным на классах
Что может быть еще интересно по этой теме
Struct в Swift похож на data class в Kotlin? По роли часто да: компактная модель данных. Но синтаксис, value semantics и возможности отличаются
Можно ли добавлять методы в struct? Да. Swift struct может иметь свойства, методы, computed properties и initializers
Почему Apple-примеры SwiftUI часто используют struct? SwiftUI views сами являются struct. Это часть декларативной модели UI
Что дальше после struct? Коллекции. Массивы структур дают хороший материал для filter, map, reduce и отображения списков в UI
Что почитать дальше по Swift
- Optionals в Swift: ?, ! и безопасное извлечение
- Массивы, словари и циклы в Swift
- SwiftUI: первый экран с Text, Button и @State
- @Binding и передача данных между SwiftUI views



