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

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

ЗАМЕТКА

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

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

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

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

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

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

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

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

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

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

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

struct SomeStructure {

// structure definition goes here

}

class SomeClass {

// class definition goes here

}

ЗАМЕТКА

Всякий раз, когда вы определяете новую структуру или класс, вы определяете новый тип Swift. Дайте Типы UpperCamelCaseимен (например, SomeStructureи SomeClassздесь) , чтобы соответствовать капитализации стандартных Swift типов (таких , как StringInt, и 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вызванным значением namenameСвойство автоматически присваивается значение по умолчанию 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. Это означает, что любые создаваемые вами структуры и экземпляры перечисления, а также любые типы значений, которые они имеют в качестве свойств, всегда копируются при передаче в ваш код.

ЗАМЕТКА

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

Рассмотрим этот пример, который использует 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изменено на ширину чуть более широкого стандарта 2К, используемого для проецирования цифрового кино (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."

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

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

Указатели

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