No history yet

Продвинутая система типов

Типы по значению и по ссылке

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

Представьте, что у вас есть рецепт. Если вы дадите его копию другу (тип-значение), любые изменения, которые он внесет в свою копию, не затронут ваш оригинал. Если же вы поделитесь ссылкой на онлайн-документ с рецептом (ссылочный тип), то любые правки вашего друга изменят и вашу версию.

Структуры (struct) и перечисления (enum) являются типами-значениями. Когда вы присваиваете их переменной или передаете в функцию, создается полная копия. Это делает ваш код безопаснее, так как исключает случайные изменения данных в другой части программы.

Классы (class), с другой стороны, являются ссылочными типами. При присваивании или передаче создается не копия объекта, а копия ссылки, указывающей на один и тот же экземпляр в памяти. Это означает, что несколько частей вашей программы могут изменять один и тот же объект.

// Тип-значение: struct
struct Point {
    var x: Int
    var y: Int
}

var p1 = Point(x: 10, y: 20)
var p2 = p1 // Создается полная копия
p2.x = 100

print(p1.x) // Выведет 10. p1 не изменился.
print(p2.x) // Выведет 100.

// Ссылочный тип: class
class View {
    var title: String
    init(title: String) { self.title = title }
}

var v1 = View(title: "Главный экран")
var v2 = v1 // Копируется ссылка, а не объект
v2.title = "Настройки"

print(v1.title) // Выведет "Настройки". v1 изменился.

Большинство стандартных типов Swift, включая Array, String и Dictionary, реализованы как структуры. Они используют оптимизацию под названием (Copy-on-Write, COW). Это означает, что полная копия данных создается только в тот момент, когда вы пытаетесь их изменить. До этого момента они ведут себя как ссылки, экономя память.

Перечисления с ассоциированными значениями

Перечисления (enums) в Swift — это не просто списки констант. Они могут хранить ассоциированные значения, что позволяет моделировать сложные состояния или данные элегантным способом. Это один из самых мощных инструментов языка.

Представьте, что вы делаете запрос к серверу. Результат может быть успешным и содержать данные, или же он может провалиться с определенной ошибкой. Вместо использования нескольких опциональных переменных для хранения этих состояний, можно использовать одно перечисление.

enum NetworkResult {
    case success(data: Data)
    case failure(error: Error)
    case loading
}

func handle(result: NetworkResult) {
    switch result {
    case .success(let data):
        print("Получены данные: \(data.count) байт")
    case .failure(let error):
        print("Произошла ошибка: \(error.localizedDescription)")
    case .loading:
        print("Идет загрузка...")
    }
}

Такой подход делает код типобезопасным. Вы не сможете случайно получить доступ к данным, если произошла ошибка, и наоборот. Компилятор заставит вас обработать все возможные случаи в блоке switch, что снижает вероятность ошибок.

Свойства и их поведение

Кроме хранимых свойств (stored properties), которые просто хранят значение, в Swift есть вычисляемые свойства и наблюдатели свойств.

Вычисляемое свойство

noun

Свойство, которое не хранит значение напрямую, а вычисляет его на основе других свойств и возвращает результат. Оно должно иметь геттер (getter) и может иметь сеттер (setter).

struct Rect {
    var origin = Point(x: 0, y: 0)
    var size = (width: 10, height: 10)

    // Вычисляемое свойство
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

А что, если нужно выполнить какое-то действие сразу после изменения свойства? Для этого существуют наблюдатели свойств willSet и didSet.

willSet вызывается прямо перед тем, как значение будет сохранено. Он получает новое значение в качестве параметра (по умолчанию newValue). didSet вызывается сразу после сохранения нового значения. Он получает старое значение в качестве параметра (по умолчанию oldValue).

class Game {
    var score = 0 {
        willSet {
            print("Счет вот-вот изменится на \(newValue)!")
        }
        didSet {
            print("Счет изменился с \(oldValue) на \(score).")
            // Здесь можно, например, обновить UI
        }
    }
}

let game = Game()
game.score = 100

Результат выполнения кода выше: "Счет вот-вот изменится на 100!" "Счет изменился с 0 на 100."

Изменение структур

Поскольку структуры являются типами-значениями, Swift по умолчанию защищает их от изменений внутри собственных методов. Если метод структуры должен изменить одно из ее свойств, его нужно пометить ключевым словом mutating.

Это ключевое слово явно говорит, что метод будет изменять состояние структуры. При вызове такого метода для экземпляра, объявленного через let, компилятор выдаст ошибку, что обеспечивает дополнительную безопасность.

struct Player {
    var level = 1
    var experience = 0

    // Этот метод изменяет свойства структуры
    mutating func gainExperience(_ points: Int) {
        experience += points
        if experience >= 1000 {
            level += 1
            experience = 0
            print("Уровень повышен!")
        }
    }
}

var player = Player()
player.gainExperience(500)
player.gainExperience(600) // "Уровень повышен!"

let constantPlayer = Player()
// constantPlayer.gainExperience(100) // Ошибка компиляции!

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

Quiz Questions 1/5

В чем заключается основное различие между struct и class в Swift?

Quiz Questions 2/5

Что такое механизм "Копирование-при-записи" (Copy-on-Write) в Swift?