Функции, struct и class в Swift

В этом уроке мы сделаем модель товара через struct, напишем функцию расчета скидки, добавим метод и отдельно посмотрим, чем class отличается от struct на простом примере. Это важный мост от первых переменных к реальным Swift-моделям

К концу урока вы поймете, почему Swift часто начинает с struct, когда нужен тип данных, и почему class выбирают не «потому что так в ООП», а когда нужна ссылочная семантика

Что получится в конце

Код:

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

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x