Spec-Zone.ru › Swift Language

Структуры и классы

Модель пользовательских типов, которые инкапсулируют данные.

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

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

Примечание: экземпляр класса традиционно называется объектом. Однако структуры и классы Swift гораздо ближе по функциональности, чем в других языках, и большая часть этой главы описывает функциональность, которая применима к экземплярам любого типа класса или структуры. Поэтому используется более общее понятие экземпляр.

Сравнение структур и классов

Структуры и классы в Swift имеют много общего. Оба могут:

  • Определять свойства для хранения значений
  • Определять методы для предоставления функциональности
  • Определять индексы для доступа к своим значениям с использованием синтаксиса индексов
  • Определять инициализаторы для настройки их начального состояния
  • Расширяться для расширения их функциональности за пределами реализации по умолчанию
  • Соответствовать протоколам для предоставления стандартной функциональности определенного типа

Для получения дополнительной информации см. Свойства, Методы, Индексы, Инициализация, Расширения и Протоколы.

Классы обладают дополнительными возможностями, которых нет у структур:

  • Наследование позволяет одному классу унаследовать характеристики другого.
  • Приведение типов позволяет проверить и интерпретировать тип экземпляра класса во время выполнения.
  • Деинициализаторы позволяют экземпляру класса освободить любые ресурсы, которые он назначил.
  • Счётчик ссылок позволяет иметь более одной ссылки на экземпляр класса.

Для получения дополнительной информации см. Наследование, Приведение типов, Деинициализация и Автоматическое управление ссылками.

Дополнительные возможности, поддерживаемые классами, связаны с повышенной сложностью. В качестве общего руководства, отдавайте предпочтение структурам, так как они легче поддаются осмыслению, а классы используйте, когда они уместны или необходимы. На практике это означает, что большинство пользовательских типов, которые вы определяете, будут структурами и перечислениями. Более подробное сравнение см. в Выбор между структурами и классами.

Примечание: Классы и акторы обладают многими общими характеристиками и поведением. Сведения об акторах см. в Конкурентность.

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

Структуры и классы имеют похожий синтаксис определения. Структуры объявляются с помощью ключевого слова struct, а классы — с помощью ключевого слова class. Оба размещают своё полное определение внутри пары фигурных скобок:

struct SomeStructure {
    // structure definition goes here
}
class SomeClass {
    // class definition goes here
}

Примечание: Всякий раз, когда вы определяете новую структуру или класс, вы определяете новый тип Swift. Давайте типам UpperCamelCase имена (например, SomeStructure и SomeClass здесь), соответствующие регистру символов стандартных типов Swift (например, String, Int и Bool). Давайте свойствам и методам lowerCamelCase имена (например, frameRate и incrementCount), чтобы отличить их от имён типов.

Вот пример определения структуры и определения класса:

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

В примере выше определяется новая структура, называемая Resolution, для описания разрешения дисплея на основе пикселей. Эта структура имеет два хранимых свойства, называемых width и height. Хранимые свойства — это константы или переменные, которые объединяются и хранятся как часть структуры или класса. Эти два свойства предполагаются типа Int, установив их начальное целое значение 0.

В примере выше также определён новый класс, называемый VideoMode, для описания определённого видеорежима для видеодисплея. Этот класс имеет четыре переменных хранимых свойства. Первое, resolution, инициализируется с помощью нового экземпляра структуры Resolution, что предполагает тип свойства Resolution. Для других трёх свойств будут инициализированы новые экземпляры VideoMode с установкой interlaced, значением false (означающим «нечересстрочевое видео»), частотой кадров воспроизведения 0.0 и необязательным значением String, которое называется name. Свойство name автоматически получает значение по умолчанию nil или «нет значения name», поскольку оно имеет необязательный тип.

Экземпляры структур и классов

Определения структур Resolution и класса VideoMode описывают только то, как будет выглядеть Resolution или VideoMode. Они сами по себе не описывают конкретное разрешение или видеорежим. Для этого необходимо создать экземпляр структуры или класса.

Синтаксис создания экземпляров очень похож для структур и классов:

let someResolution = Resolution()
let someVideoMode = VideoMode()

Структуры и классы оба используют синтаксис инициализатора для новых экземпляров. Самый простой вариант синтаксиса инициализатора использует имя типа класса или структуры, за которым следуют пустые скобки, такие как Resolution() или VideoMode(). Это создаёт новый экземпляр класса или структуры, при этом все свойства инициализируются их значениями по умолчанию. Инициализация классов и структур описана более подробно в Инициализации.

Доступ к свойствам

Вы можете получить доступ к свойствам экземпляра, используя синтаксис точки. В синтаксисе точки вы записываете имя свойства непосредственно после имени экземпляра, разделённого точкой (.), без пробелов:

print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"

В этом примере someResolution.width относится к свойству width объекта someResolution и возвращает его начальное значение по умолчанию 0.

Можно углубиться в подсвойства, такие как свойство width в свойстве resolution объекта VideoMode:

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"

Вы также можете использовать синтаксис точки для присвоения нового значения переменным свойствам:

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"

Инициализаторы по членам для типов структуры

Все структуры имеют автоматически сгенерированный инициализатор по членам, который можно использовать для инициализации свойств членов новых экземпляров структур. Начальные значения для свойств нового экземпляра могут передаваться в инициализатор по членам по имени:

let vga = Resolution(width: 640, height: 480)

В отличие от структур, экземпляры классов не получают инициализатор по членам по умолчанию. Инициализаторы описаны более подробно в Инициализации.

Структуры и перечисления являются типами значений

Тип значения — это тип, значение которого копируется при присваивании переменной или константе, или при передаче в функцию.

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

Все структуры и перечисления являются типами значений в Swift. Это означает, что все экземпляры структур и перечислений, которые вы создаёте — и любые типы значений, которые они имеют в качестве свойств, — всегда копируются, когда они передаются в вашем коде.

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

Рассмотрим этот пример, который использует структуру Resolution из предыдущего примера:

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

В этом примере объявляется константа, называемая hd, и устанавливается значение экземпляра Resolution, инициализированного шириной и высотой видео Full HD (ширина 1920 пикселей и высота 1080 пикселей).

Затем объявляется переменная, называемая cinema, и устанавливается текущее значение hd. Так как Resolution является структурой, создаётся копия существующего экземпляра, и эта новая копия присваивается переменной cinema. Несмотря на то, что у hd и cinema теперь одинаковая ширина и высота, они — два совершенно разных экземпляра за кулисами.

Далее свойство width объекта cinema изменяется на ширину немного более широкого стандарта 2K, используемого для цифровой проекции кино (ширина 2048 пикселей и высота 1080 пикселей):

cinema.width = 2048

Проверка свойства width объекта cinema показывает, что оно действительно изменилось на 2048:

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"

Однако свойство width исходного экземпляра hd всё ещё имеет старое значение 1920:

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

Когда cinema было присвоено текущее значение hd, значения, хранящиеся в hd, были скопированы в новый экземпляр cinema. В результате получились два совершенно отдельных экземпляра, содержащие одинаковые числовые значения. Однако, поскольку они являются отдельными экземплярами, установка ширины cinema на 2048 не влияет на ширину, хранящуюся в hd, как показано на рисунке ниже:

То же самое поведение относится к перечислениям:

enum CompassPoint {
    case north, south, east, west
    mutating func turnNorth() {
        self = .north
    }
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()

print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"

Когда rememberedDirection присваивается значение currentDirection, на самом деле оно устанавливается на копию этого значения. Изменение значения currentDirection после этого не влияет на копию исходного значения, хранящегося в rememberedDirection.

Классы являются типами ссылок

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

Вот пример, использующий класс VideoMode, определённый выше:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

В этом примере объявляется новая константа, называемая tenEighty, и ей присваивается новый экземпляр класса VideoMode. Режим видео получает копию разрешения HD 1920 от 1080, установленного ранее. Он настроен как чересстрочный, его имя установлено как "1080i", а частота кадров — как 25.0 кадров в секунду.

Далее, tenEighty присваивается новой константе, называемой alsoTenEighty, а частота кадров alsoTenEighty изменяется:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

Поскольку классы являются ссылками, tenEighty и alsoTenEighty фактически оба ссылаются на один и тот же экземпляр VideoMode. По сути, они — просто два разных имени для одного и того же экземпляра, как показано на рисунке ниже:

Проверка свойства frameRate экземпляра tenEighty показывает, что оно правильно сообщает о новой частоте кадров 30.0 от основного экземпляра VideoMode:

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"

Этот пример также демонстрирует, как с ссылками сложнее работать. Если tenEighty и alsoTenEighty находятся далеко в коде программы, то может быть сложно найти все способы изменения режима видео. В любом месте, где вы используете tenEighty, вам также необходимо учитывать код, использующий alsoTenEighty, и наоборот. В отличие от этого, с типами значений работать проще, потому что весь код, взаимодействующий с одним и тем же значением, находится рядом в файлах исходного кода.

Обратите внимание, что tenEighty и alsoTenEighty объявлены как константы, а не переменные. Однако вы всё равно можете изменить tenEighty.frameRate и alsoTenEighty.frameRate, так как значения самих констант tenEighty и alsoTenEighty фактически не изменяются. tenEighty и alsoTenEighty сами по себе не «хранят» экземпляр VideoMode — вместо этого они оба ссылаются на экземпляр VideoMode за кулисами. Изменяется свойство frameRate основного экземпляра VideoMode, а не значения константных ссылок на этот экземпляр VideoMode.

Операторы тождества

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

Иногда может быть полезно узнать, ссылаются ли две константы или переменные на один и тот же экземпляр класса. Для этого Swift предоставляет два оператора тождества:

  • Тождественно равны (===)
  • Не тождественно равны (!==)

Используйте эти операторы для проверки, ссылаются ли две константы или переменные на один и тот же экземпляр:

if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."

Обратите внимание, что тождественно равны (представлены тремя знаками равенства, или ===) — это не то же самое, что равны (представлены двумя знаками равенства, или ==). Тождественно равны означает, что две константы или переменные типа класса ссылаются на один и тот же экземпляр класса. Равны означает, что два экземпляра считаются равными или эквивалентными по значению, в соответствии с соответствующим значением равенства, определённым разработчиком типа.

При определении собственных пользовательских структур и классов вы должны сами решить, что считать двумя равными экземплярами. Процесс определения собственных реализаций операторов == и != описан в <doc:AdvancedOperators#Equivalence-Operators>.

Указатели

Если вы знакомы с языками C, C++ или Objective-C, вы можете знать, что в этих языках используются указатели для ссылки на адреса в памяти. Swift-константа или -переменная, ссылающаяся на экземпляр некоторого типа ссылок, похожа на указатель в C, но не является прямым указателем на адрес в памяти и не требует записи звёздочки (*), чтобы указать на создание ссылки. Вместо этого эти ссылки определяются как любая другая константа или переменная в Swift. Стандартная библиотека Swift предоставляет типы указателей и буферов, которые можно использовать, если вам нужно взаимодействовать с указателями напрямую — см. Управление памятью вручную.

This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API