Swift поставляется со многими полезными типами для представления таких данных, как числа, текст, коллекции и значения true или false. Но по мере того, как вы начнете создавать приложения, вы обнаружите, что хотите создавать свои собственные типы данных со свойствами и функциями собственного дизайна.
Вы создаете пользовательский тип данных, объявляя структуру. Структура объединяет одну или несколько переменных в один тип. Вы можете определить функциональность, добавив в структуру методы типа и экземпляра.
В этом уроке вы узнаете, как создавать структуры, и узнаете, как структуры формируют строительные блоки вашего кода.
Что Вы Узнаете
- Как создать пользовательскую структуру
- Как определить свойства структуры
- Как добавить методы или функции в структуру
Словарь
Связанные ресурсы
Руководство по языку программирования Swift: Классы и структуры
Возможно, вы этого не осознали, но вы работаете со структурами с самого начала этого курса. Swift поставляется с предопределенными структурами для представления общих типов данных. Вы можете определить свои собственные структуры, если вам нужен тип, соответствующий конкретным потребностям вашей программы или приложения.
В своей простейшей форме структура - это именованная группа из одного или нескольких свойств, составляющих тип. Свойства представляют информацию об экземпляре структуры.
Вы определяете структуру, используя ключевое слово struct вместе с уникальным именем. Затем вы можете определить свойства как часть struct, перечислив объявления констант или переменных с соответствующей аннотацией типа. Соглашение заключается в том, чтобы писать имена типов с заглавной буквы и использовать верблюжий регистр для имен свойств.
Рассмотрим простое объявление структуры Person со свойством name:
struct Person {
var name: String
}
Приведенное выше объявление просто определяет свойства типа Person. Это не имеет ценности само по себе. Для этого вам необходимо создать экземпляр типа Person. Затем вы можете получить доступ к данным, хранящимся в его свойствах, таким как имя пользователя name, используя синтаксис точки.
let firstPerson = Person(name: “Jasmine”)
print(firstPerson.name)
Console Output:
Jasmine
Как следует из названия в примере, за время существования вашей программы может быть создано более одного пользователя Person. Следующий созданный вами экземпляр может называться secondPerson, или вы можете создать коллекцию под названием people, содержащую несколько объектов Person. Вы узнаете о коллекциях в следующем уроке.
Вы можете добавить функциональность к структуре, добавив метод. Метод - это функция, которая назначена определенному типу. В этом примере наши экземпляры Person теперь могут sayHello.
struct Person {
var name: String
func sayHello() {
print(”Hello, there! My name is \(name)!”)
}
}
Теперь вы можете вызвать метод экземпляра напрямую, используя синтаксис dot.
let person = Person(name: “Jasmine”)
person.sayHello()
Console Output:
Hello, there! My name is Jasmine!
В следующих разделах вы узнаете больше об экземплярах и методах экземпляра.
Экземпляры
Вы только что узнали, что структура определяет новый тип. Чтобы использовать этот тип, вы должны создать его экземпляр, процесс, называемый инициализацией. После инициализации каждый экземпляр наследует все свойства и функции структуры.
В следующем примере объекты myShirt и yourShirt - объекты Shirt имеют свойства size и color. Но каждый из них представляет собой отдельную рубашку с различными значениями для каждого свойства и, следовательно, отдельный экземпляр типа Shirt. Типы Size и Color определяют группу доступных параметров, называемых перечислением, о которых вы узнаете в следующем уроке. На данный момент, поскольку вы не определили Size и Color, этот пример не будет компилироваться, если вы скопируете его на игровую площадку. Вместо этого просто посмотрите на код и попытайтесь концептуально понять, что происходит.
struct Shirt {
var size: Size
var color: Color
}
// Defines the attributes of a shirt.
let myShirt = Shirt(size: .xl, color: .blue)
// Creates an instance of an individual shirt.
let yourShirt = Shirt(size: .m, color: .red)
// Creates a separate instance of an individual shirt.
Вот еще один пример определения структуры, на этот раз с добавленной функциональностью. Структура определяет атрибуты и функциональность объекта Car и описывает firstCar и secondCar как два экземпляра.
struct Car {
var make: String
var model: String
var year: Int
var topSpeed: Int
func startEngine() {
print(”The \(year) \(make) \(model)’s engine has started.”)
}
func drive() {
print(”The \(year) \(make) \(model) is moving.”)
}
func park() {
print(”The \(year) \(make) \(model) is parked.”)
}
}
let firstCar = Car(make: “Honda”, model: “Civic”, year: 2010,
topSpeed: 120)
let secondCar = Car(make: “Ford”, model: “Fusion”, year: 2013,
topSpeed: 125)
firstCar.startEngine()
firstCar.drive()
Console Output:
The 2010 Honda Civic’s engine has started.
The 2010 Honda Civic is moving.
Что делали машины в этом коде? Если вы представите, что firstCar и secondCar стоят на одной подъездной дорожке, то после выполнения этого кода только firstCar сдвинулся с места, в то время как secondCar даже не запустил двигатель.
Инициализаторы
Все структуры поставляются по крайней мере с одним инициализатором. Инициализатор аналогичен функции, которая возвращает новый экземпляр типа. Многие распространенные типы имеют инициализатор по умолчанию без аргументов, init().
Экземпляры, созданные с помощью этого инициализатора, имеют значение по умолчанию. String по умолчанию - “”, значение Int по умолчанию равно 0, а значение Bool по умолчанию равно false:
var string = String.init() // “”
var integer = Int.init() // 0
var bool = Bool.init() // false
Но существует сокращенный синтаксис для инициализаторов, который гораздо более распространен. Код в следующем фрагменте кода более лаконичен, но работает так же, как в приведенном выше примере:
var string = String() // “”
var integer = Int() // 0
var bool = Bool() // false
Всякий раз, когда вы определяете новый тип, вы должны подумать о том, как вы будете создавать новые экземпляры. В этом уроке рассматриваются различные подходы к инициализации значений свойств.
Значения по умолчанию
Во время инициализации новых экземпляров Swift требует, чтобы вы установили значения для всех свойств экземпляра.
Один из подходов заключается в предоставлении значений свойств по умолчанию в определении типа. Каждый экземпляр инициализируется этими значениями. Это полезно при определении объектов, которые имеют согласованное состояние по умолчанию, например нулевое значение на одометре.
Если вы укажете значения по умолчанию для всех свойств экземпляра структуры, компилятор Swift сгенерирует для вас инициализатор по умолчанию.
В предыдущем разделе вы видели значения по умолчанию для String, Int и Bool. Используя значения свойств по умолчанию, вы можете создать состояние по умолчанию для каждого нового экземпляра ваших пользовательских типов.
struct Odometer {
var count: Int = 0
}
let odometer = Odometer()
print(odometer.count)
Console Output:
0
Обратите внимание, что при объявлении свойства значение count по умолчанию равно 0. Все новые экземпляры Odometer будут созданы с этим значением по умолчанию.
Почленные инициализаторы
Когда вы определяете новую структуру и не объявляете свои собственные инициализаторы, Swift создает специальные инициализаторы, называемые почленными инициализаторами (memberwise initializers), которые позволяют вам устанавливать начальные значения для каждого свойства нового экземпляра.
let odometer = Odometer(count: 27000)
print(odometer.count)
Console Output:
27000
Почленные инициализаторы - это правильный подход, когда для новых экземпляров вашего типа нет состояния по умолчанию.
Рассмотрим структуру Person со свойством name. Что бы вы назначили в качестве значения по умолчанию для name?
struct Person {
var name: String
}
На первый взгляд, вы можете сказать, что значением по умолчанию для name может быть “". Но пустая строка String - это не name, и вы не хотите случайно инициализировать Person с пустым name.
Чтобы вызвать почленный инициализатор, используйте имя типа, за которым следуют круглые скобки, содержащие параметры, соответствующие каждому свойству. На самом деле, вы видели почленные инициализаторы в различных фрагментах кода на протяжении всего этого урока:
struct Person {
var name: String
func sayHello() {
print(”Hello, there!”)
}
}
let person = Person(name: “Jasmine”) // Memberwise initializer
struct Shirt {
var size: Size
var color: Color
}
let myShirt = Shirt(size: .xl, color: .blue) // Memberwise Initializer
struct Car {
var make: String
var model: String
var year: Int
var topSpeed: Int
}
let firstCar = Car(make: “Honda”, model: “Civic”, year: 2010, topSpeed: 120) // Memberwise initializer
Вы можете определить структуру, для которой некоторые свойства имеют разумные значения по умолчанию, а другие - нет. Например, банковский счет может начинаться с нулевого баланса по умолчанию, но требовать уникального номера счета.
struct BankAccount {
var accountNumber: Int
var balance: Double = 0
}
В этом случае вы получите два почленных инициализатора: один, который предоставляет параметры для каждого свойства, а другой, который предоставляет параметры только для свойств без значений по умолчанию.
var newAccount = BankAccount(accountNumber: 123)
var transferredAccount = BankAccount(accountNumber: 456,
balance: 1200)
Почленные инициализаторы - это наиболее распространенный способ создания новых экземпляров ваших пользовательских структур. Но могут быть случаи, когда вы захотите определить инициализатор, который завершает некоторую пользовательскую логику перед назначением всех свойств. В этих случаях вы можете определить пользовательский инициализатор.
Пользовательские инициализаторы
Вы можете настроить процесс инициализации, определив свой собственный инициализатор. Пользовательские инициализаторы имеют те же требования, что и инициализаторы по умолчанию и memberwise: для всех свойств должны быть установлены начальные значения перед завершением инициализации.
Рассмотрим структуру Temperature со свойством celsius. Если у вас есть доступ к температуре в градусах Цельсия, вы можете инициализировать ее с помощью почленного инициализатора.
struct Temperature {
var celsius: Double
}
let temperature = Temperature(celsius: 30.0)
Но если у вас есть доступ к температуре в градусах Фаренгейта, вам нужно будет преобразовать это значение в градусы Цельсия перед использованием почленного инициализатора.
let fahrenheitValue = 98.6
let celsiusValue = (fahrenheitValue - 32) / 1.8
let temperature = Temperature(celsius: celsiusValue)
Но почленный инициализатор требовал, чтобы вы вычисляли значение Цельсия перед инициализацией нового объекта Temperature. Вместо этого вы можете создать пользовательский инициализатор, который принимает значение по Фаренгейту в качестве параметра, выполняет вычисление и присваивает значение свойству celsius.
struct Temperature {
var celsius: Double
init(celsius: Double) {
self.celsius = celsius
}
init(fahrenheit: Double) {
celsius = (fahrenheit - 32) / 1.8
}
}
let currentTemperature = Temperature(celsius: 18.5)
let boiling = Temperature(fahrenheit: 212.0)
print(currentTemperature.celsius)
print(boiling.celsius)
Console Output:
18.5
100.0
Вы можете заметить, что в приведенном выше примере есть почленный инициализатор. Когда вы добавляете пользовательский инициализатор в определение типа, вы должны определить свои собственные почленные инициализаторы и инициализаторы по умолчанию; Swift больше не предоставляет их для вас.
Вы можете добавить несколько пользовательских инициализаторов. Приведенный ниже код переопределяет Temperature, добавляя инициализатор Кельвина и инициализатор по умолчанию.
struct Temperature {
var celsius: Double
init(celsius: Double) {
self.celsius = celsius
}
init(fahrenheit: Double) {
celsius = (fahrenheit - 32) / 1.8
}
init(kelvin: Double) {
celsius = kelvin - 273.15
}
init() {
celsius = 0
}
}
let currentTemperature = Temperature(celsius: 18.5)
let boiling = Temperature(fahrenheit: 212.0)
let absoluteZero = Temperature(kelvin: 0.0)
let freezing = Temperature()
print(currentTemperature.celsius)
print(boiling.celsius)
print(absoluteZero.celsius)
print(freezing.celsius)
Console Output:
18.5
100.0
0
Каждый экземпляр Temperature создается с использованием другого инициализатора и другого значения, но каждый заканчивается как объект Temperature с требуемым свойством celsius.
Методы экземпляра
Методы экземпляра - это функции, которые могут быть вызваны для определенных экземпляров типа. Они предоставляют способы доступа к свойствам структуры и изменения их, а также добавляют функциональность, связанную с назначением экземпляра.
Как вы узнали ранее, вы добавляете метод экземпляра, добавляя функцию в определение типа. Затем вы можете вызвать эту функцию для экземпляров этого типа. Возможно, вы уже замечали этот синтаксис в структурах Person или Car ранее.
Рассмотрим структуру Size с методом экземпляра area(), который вычисляет площадь конкретного экземпляра путем умножения его ширины и высоты:
struct Size {
var width: Double
var height: Double
func area() -> Double {
width * height
}
}
let someSize = Size(width: 10.0, height: 5.5)
let area = someSize.area() // Area is assigned a value of 55.0
Экземпляр someSize относится к типу Size, а width и height являются его свойствами. Area() - это метод экземпляра, который может быть вызван для всех экземпляров типа Size.
Методы изменений
Иногда вам захочется обновить значения свойств структуры в методе экземпляра. Для этого вам нужно будет добавить ключевое слово mutating перед функцией.
В следующем примере простая структура хранит данные о пробеге для конкретного объекта Car. Прежде чем ознакомиться с кодом, подумайте, какие данные должен хранить счетчик пробега и какие действия он должен выполнять.
- Сохраните количество пробега, которое будет отображаться на одометре
- Увеличьте количество пробега, чтобы обновить пробег при движении автомобиля
- Возможно, сбросьте счетчик пробега, если автомобиль проедет больше количества миль, которое может отображаться на одометре.
struct Odometer {
var count: Int = 0 // Assigns a default value to the `count` property.
mutating func increment() {
count += 1
}
mutating func increment(by amount: Int) {
count += amount
}
mutating func reset() {
count = 0
}
}
var odometer = Odometer() // odometer.count defaults to 0
odometer.increment() // odometer.count is incremented to 1
odometer.increment(by: 15) // odometer.count is incremented to 16
odometer.reset() // odometer.count is reset to 0
Экземпляр odometer относится к типу Odometer, а increment() и increment(by:) - это методы экземпляра, которые добавляют мили к экземпляру. Метод экземпляра reset() сбрасывает счетчик пробега до нуля.
Вычисляемые свойства
В Swift есть функция, которая позволяет свойству выполнять логику, возвращающую вычисленное значение.
Рассмотрим пример с Temperature. В то время как в большинстве стран мира для измерения температуры используется шкала Цельсия, в некоторых местах (например, в США) используется градус Фаренгейта, а в некоторых профессиях используется градус Кельвина. Таким образом, может быть полезно, чтобы структура Temperature поддерживала все три системы измерения.
struct Temperature {
var celsius: Double
var fahrenheit: Double
var kelvin: Double
}
Представьте, что вы использовали почленный инициализатор для этой структуры:
let temperature = Temperature(celsius: 0, fahrenheit: 32.0, kelvin: 273.15)
Каждый раз, когда вы будете писать код для инициализации объекта Temperature, вам нужно будет вычислить каждую температуру и передать все эти значения в качестве параметров.
Альтернативой было бы добавить несколько инициализаторов, которые обрабатывают вычисления.
struct Temperature {
var celsius: Double
var fahrenheit: Double
var kelvin: Double
init(celsius: Double) {
self.celsius = celsius
fahrenheit = celsius * 1.8 + 32
kelvin = celsius + 273.15
}
init(fahrenheit: Double) {
self.fahrenheit = fahrenheit
celsius = (fahrenheit - 32) / 1.8
kelvin = celsius + 273.15
}
init(kelvin: Double) {
self.kelvin = kelvin
celsius = kelvin - 273.15
fahrenheit = celsius * 1.8 + 32
}
}
let currentTemperature = Temperature(celsius: 18.5)
let boiling = Temperature(fahrenheit: 212.0)
let freezing = Temperature(kelvin: 273.15)
Описанный выше подход, использующий несколько инициализаторов, включает в себя управление большим количеством состояний или информации. Каждый раз, когда температура меняется, вам потребуется обновить все три свойства. Такой подход подвержен ошибкам и был бы неприятен любому программисту. Swift обеспечивает более безопасный подход. С помощью вычисляемых свойств вы можете создавать свойства, которые могут вычислять их значение на основе других свойств экземпляра или логики.
struct Temperature {
var celsius: Double
var fahrenheit: Double {
celsius * 1.8 + 32
}
var kelvin: Double {
celsius + 273.15
}
}
Чтобы добавить вычисляемое свойство, вы объявляете это свойство как переменную (поскольку его значение может изменяться). Вы также должны явно объявить тип. Затем вы используете открытую фигурную скобку ({) и закрывающую фигурную скобку (}), чтобы определить логику, которая вычисляет возвращаемое значение.
Вы можете получить доступ к свойствам компьютера, используя синтаксис точки, точно так же, как и с любым другим свойством.
let currentTemperature = Temperature(celsius: 0.0)
print(currentTemperature.fahrenheit)
print(currentTemperature.kelvin)
Console Output:
32.0
273.15
Логика, содержащаяся в вычисляемом свойстве, будет выполняться при каждом обращении к свойству, поэтому возвращаемое значение всегда будет обновляться.
Наблюдатели за недвижимостью
Swift позволяет вам наблюдать за любым свойством и реагировать на изменения в его стоимости. Эти наблюдатели свойств вызываются каждый раз, когда устанавливается значение свойства, даже если новое значение совпадает с текущим значением свойства. Существует два замыкания наблюдателя, или блока кода, которые вы можете определить для любого заданного свойства: willSet и didSet.
В следующем примере счетчик шагов был определен со свойством totalSteps. Были определены как наблюдатели willSet, так и didSet. Всякий раз, когда totalSteps изменяется, сначала вызывается willSet, и у вас будет доступ к новому значению, которое будет установлено в значение свойства в константе с именем newValue. После обновления значения свойства будет вызван didSet, и вы сможете получить доступ к предыдущему значению свойства с помощью oldValue.
struct StepCounter {
var totalSteps: Int = 0 {
willSet {
print(”About to set totalSteps to \(newValue)”)
}
didSet {
if totalSteps > oldValue {
print(”Added \(totalSteps - oldValue) steps”)
}
}
}
}
Вот результат при изменении свойства totalSteps нового счетчика шагов StepCounter:
var stepCounter = StepCounter()
stepCounter.totalSteps = 40
stepCounter.totalSteps = 100
Console Output:
About to set totalSteps to 40
Added 40 steps
About to set totalSteps to 100
Added 60 steps
Свойства и методы типов
Вы узнали, что свойства экземпляра - это данные об отдельном экземпляре типа, а методы экземпляра - это функции, которые можно вызывать для отдельных экземпляров типа.
Swift также поддерживает добавление свойств и методов типа, к которым можно получить доступ или вызвать сам тип. Используйте ключевое слово static, чтобы добавить свойство или метод к типу.
Свойства типа полезны, когда свойство связано с типом, но не является характеристикой самого экземпляра.
Следующий пример определяет структуру Temperature, которая имеет статическое свойство с именем boilingPoint, которое является постоянным значением для всех экземпляров Temperature.
struct Temperature {
static var boilingPoint = 100
}
Вы получаете доступ к свойствам типа, используя синтаксис точки в имени типа.
let boilingPoint = Temperature.boilingPoint
Методы типов аналогичны свойствам типов. Используйте метод типа, когда действие связано с типом, но не является чем-то, что должен выполнять конкретный экземпляр типа.
Структура Double, определенная в стандартной библиотеке Swift, содержит статический метод с именем minimum который возвращает меньший из двух его параметров..
let smallerNumber = Double.minimum(100.0, -1000.0)
Копирование
Если вы присваиваете структуру переменной или передаете экземпляр в качестве параметра в функцию, значения копируются. Таким образом, отдельные переменные являются отдельными экземплярами значения, что означает, что изменение одного значения не приводит к изменению другого.
var someSize = Size(width: 250, height: 1000)
var anotherSize = someSize
someSize.width = 500
print(someSize.width)
print(anotherSize.width)
Console Output:
500
250
Обратите внимание, что свойство width someSize изменилось на значение 500, но свойство width anotherSize не изменилось, потому что, хотя мы установили anotherSize равным someSize, это создало копию someSize, и ширина копии не изменилась при изменении исходной ширины.
Self
Возможно, вы заметили слово self в этом разделе. В Swift self ссылается на текущий экземпляр типа. Он может быть использован в методе экземпляра или вычисляемом свойстве для ссылки на его собственный экземпляр.
struct Car {
var color: Color
var description: String {
return “This is a \(self.color) car.”
}
}
Многие языки требуют использования self для ссылки на свойства экземпляра или методы в определении типа. Компилятор Swift распознает, когда для текущего объекта существуют имена свойств или методов, и делает использование self необязательным.
Этот пример функционально такой же, как и предыдущий пример.
struct Car {
var color: Color
var description: String {
return “This is a \(color) car.”
}
}
Использование self требуется в инициализаторах, имена параметров которых совпадают с именами свойств.
struct Temperature {
var celsius: Double
init(celsius: Double) {
self.celsius = celsius
}
}
Эта концепция называется затенением, и вы узнаете о ней больше в следующем уроке.
Переменные свойства
Заметили ли вы, что во всех примерах структур в этом уроке вместо констант использовались переменных свойств? Рассмотрим определение автомобиля, использованное в начале урока.
struct Car {
var make: String
var year: Int
var color: Color
var topSpeed: Int
}
Цвет автомобиля можно легко изменить с помощью покраски, а максимальная скорость может измениться, если владелец обновит двигатель. Но марка и год выпуска автомобиля никогда не изменятся, так почему бы не определить свойства с помощью let?
Свойства переменных обеспечивают удобный способ создания новых данных из старых данных. В следующем коде создание синего Ford 2010 года выпуска так же просто, как создание копии Honda 2010 года выпуска и изменение свойства make. Если бы свойство make было постоянным, то вторую машину нужно было бы создать с помощью почленного инициализатора.
var firstCar = Car(make: “Honda”, year: 2010, color: .blue, topSpeed: 120)
var secondCar = firstCar
secondCar.make = “Ford”
Ранее в этом уроке вы узнали, что всякий раз, когда экземпляр структуры присваивается новой переменной, значения копируются. В предыдущем примере firstCar по-прежнему является Honda. Если вы хотите явно запретить когда-либо изменять свойства firstCar, просто объявите экземпляр с помощью let.
let firstCar = Car(make: “Honda”, year: 2010, color: .blue, topSpeed: 120)
firstCar.color = .red // Compiler error!
Даже если свойства объявлены с использованием var, значения содержимого не могут быть изменены. Как правило, вы должны использовать let, когда это возможно, для определения экземпляра структуры, использовать var, если экземпляр необходимо изменить, и использовать var при определении свойств структуры.
Лабораторная работа
Откройте и завершите упражнения в Lab—Structures.playground.
Подключение к дизайну
В рабочей тетради по разработке приложений подумайте о типах данных, которые могут потребоваться в вашем приложении, и о том, как вы могли бы смоделировать эти данные. Будут ли вам нужны только предопределенные типы, такие как числа и строки, или было бы полезно определить свой собственный тип данных со структурой? Что касается структур, опишите свойства и методы, которые они могут иметь. Делайте комментарии в разделе Карты или на новом пустом слайде в конце документа.
В примере приложения Go Green для рабочей книги для каждого журнала мусора или утилизации может существовать структура, называемая ItemEntry. Структура может иметь свойства ItemType, date, weight и name. Он также может иметь метод под названием environmentalImpact(), который будет вычислять количество CO2 или другого загрязнения предмета на основе его названия (например, "бумага") и weight.
Отрывок из книги
Develop in Swift Fundamentals
Apple Education
https://books.apple.com/ru/book/develop-in-swift-fundamentals/id1581182804