Структуры

09 ноября 2015

Используя основные строительные блоки, такие как переменные и константы, вы можете подумать, что вы готовы завоевать мир! Ну или почти завоевать ;). Большинство программ, которые выполняют сложные задачи, требуют более высоких уровней абстракции. Другими словами, в дополнение к Int, String или Array, нужны новые типы, специфичные для решения конкретных задач. Этот туториал познакомит вас со структурами, которые являются "именованным типом". Также как с Int, String или Array, вы можете определить свою собственную структуру для создания именованных типов, чтобы позже использовать их в коде. К концу этого урока, вы будете знать, как определить и использовать свои собственные структуры.

Итак, начнем.

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

let latitude: Double = 44.9871
let longitude: Double = -93.2758
let range: Double = 200.0

func isInRange(lat: Double, long: Double) -> Bool {
  // А вы  думали, сидя на математике,
  // что никогда вам не пригодится теорема Пифагора, верно? :)
  let difference = sqrt(pow((latitude - lat), 2) +
    pow((longitude - long), 2))
  let distance = difference * 0.002
  return distance < range
}

Достаточно просто, не так ли? Успешный бизнес по доставке пиццы, в конечном итоге, можно расширить, включая несколько мест в калькулятор, каждый со своим диапазоном:

let latitude_1: Double = 44.9871
let longitude_1: Double = -93.2758
 
let latitude_2: Double = 44.9513
let longitude_2: Double = -93.0942

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

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

Ваша первая структура

Структуры, или structs, являются одним из именованных типов в Swift, которые позволяют инкапсулировать связанные свойства и поведение. Вы можете определить его, дать ему имя, а затем использовать его в коде.

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

struct Location {
  let latitude: Double
  let longitude: Double
}

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

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

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

let pizzaLocation = Location(latitude: 44.9871, longitude: -93.2758)

Вместо того чтобы хранить координаты в двух отдельных переменных, вы храните их вместе!

Чтобы создать значение Location, вы используете имя типа вместе со скобками, содержащими параметры и latitude и longitude. Swift определяет этот инициализатор автоматически, и это о чем мы подробно поговорим в этом туториале.

Вы, возможно, помните, что теперь, когда бизнес по доставке пиццы расширяется, то возникнут разные диапазоны, связанные с различными ресторанами. Вы можете создать еще один структуру, которая представляет местоположение и диапазон, например так:

struct DeliveryRange {
  var range: Double
  let center: Location
}
 
let storeLocation = Location(latitude: 44.9871, longitude: -93.2758)
var pizzaRange = DeliveryRange(range: 200, center: storeLocation)

Итак теперь появилась новая структура, названная DeliveryRange, которая содержит переменную range пиццерии вместе с Location.

Доступ пользователей

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

print(pizzaRange.range) // 200

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

print(pizzaRange.center.latitude) // 44.9871

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

pizzaRange.range = 250

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

let constPizzaRange = DeliveryRange(range: 200, center: storeLocation)

// Error: change ‘let’ to ‘var’ above to make it mutable
constPizzaRange.range = 250

Теперь у вас есть и диапазон и расположение в одной структуре, как можно переписать isInRange (_ long:), чтобы принять параметр структуры вместо двух Double значений? А вот так:

let latitude: Double = 44.9871
let longitude: Double = -93.2758
 
func isInRange(deliveryRange: DeliveryRange, customer: Location) -> Bool {
  let pizzaLocation = deliveryRange.center
 
  let difference = sqrt(pow((customer.latitude - pizzaLocation.latitude), 2) +
    pow((customer.longitude - pizzaLocation.longitude), 2))
  let distance = difference * 0.002
  return distance < deliveryRange.range
}

Инициализация структуры

Когда вы определили Location и DeliveryRange структурами, Swift автоматически ищет способы создания следующих значений:

Location(latitude: 44.9871, longitude: -93.2758)

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

struct Location {
  let latitude: Double
  let longitude: Double
 
  // Строка в формате GPS "44.9871,-93.2758"
  init(coordinateString: String) {
    let crdSplit = coordinateString.characters.split(",")
    latitude = atof(String(crdSplit.first!))
    longitude = atof(String(crdSplit.last!))
  }
}

Здесь ключевое слово init определяет эту функцию в качестве инициализатора структуры Location. В этом случае инициализатор рассматривает широту и долготу вместе, как отделенную запятой строку. Это может быть полезно, если вы читаете местоположение из текстового файла.

let coords = Location(coordinateString: "44.9871,-93.2758")
print(coords.latitude) // 44.9871
print(coords.longitude) // -93.2758

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

Вводим понятие Self

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

self.latitude = atof(String(crdSplit.first!))

Вы не обязаны использовать self при написании кода внутри именованного типа, Swift выводит его автоматически, поэтому теперь вы можете писать код без него. Зачем же его тогда использовать? Это особенно полезно для инициализаторов: Когда у двух переменных в одной той же области одно имя, self может предотвратить то, что известно как shadowing (подмена параметра).

init(latitude: Double, longitude: Double) {
  latitude = latitude
  longitude = longitude
}

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

В нашем случае это аргумент, но вы ведь не этого хотели… Вместо этого, вы можете просто добавить self. перед переменной, к которой вы обращаетесь, как здесь:

init(latitude: Double, longitude: Double) {
  self.latitude = latitude
  self.longitude = longitude
}

Отсюда ясно, что вы присваиваете значения параметра свойствам.

Правила инициализаторов

Существуют несколько правил инициализаторов структур, которые исключают возможность отсутствия значения при инициализации. К концу инициализации, у структуры обязан появится набор начальных значений во всех своих свойствах хранения. Если бы вы, например, забыли присвоить значение longitude, то вы увидите сообщение об ошибке во время компиляции:

Цель проста: свойства хранения должны быть инициализированы вместе с значением.

Другие языки имеют типы, подобные структурам и могут присваивать дефолтные значения, например, значение 0 для Int. Swift, сосредоточив внимание на безопасности, гарантирует, что каждое значение указано явно.

Есть одно исключение из правила, когда хранимые свойства могут не иметь значения - опционалы!

struct ClimateControl {
  var temperature: Double
  var humidity: Double?
 
  init(temp: Double) {
    temperature = temp
  }
}

Здесь инициализатор не указывает значение для humidity. Потому что humidity является опциональным и компилятор с радостью пропустит его без значения.

Для удобства, вы можете указать другой инициализатор, который будет включать оба значения

struct ClimateControl {
  var temperature: Double
  var humidity: Double?
 
  init(temp: Double) {
    temperature = temp
  }
 
  init(temp: Double, hum: Double) {
    temperature = temp
    humidity = hum
  }
}

Теперь вы можете создавать значения ClimateControl с или без значения влажности:

let ecoMode = ClimateControl(temp: 75.0)
let dryAndComfortable = ClimateControl(temp: 71.0, hum: 30.0)

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

Заметка

 

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

Вводим методы

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

let pizzaJoints = [
  DeliveryRange(location: Location(coordinateString: "44.9871,-93.2758")),
  DeliveryRange(location: Location(coordinateString: "44.9513,-93.0942")),
]
 
func isInRange(location: customer) -> Bool {
  for pizzaRange in pizzaJoints {
    let difference = sqrt(pow((latitude - lat), 2) + pow((longitude - long), 2))
    if (difference < joint.range) {
      return true
    }
  }
  return false
}
 
let customer = Location(“44.9850,-93.2750")
 
print(isInRange(location: customer)) // Pizza time!

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

Идея о нахождении "в диапазоне" тесно связана с характеристиками одной пиццерии. На самом деле, все расчеты в nilisInRange происходят в одном месте одновременно. Было бы замечательно, если бы сам DeliveryRange мог сказать вам, сможет ли ресторан осуществить доставку определенному клиенту?

Во многом, как структура может иметь константы и переменные, так она может определять свои собственные функции:

struct DeliveryRange {
  var range: Double
  let center: Location
 
  func isInRange(customer: Location) -> Bool {
    let difference = sqrt(pow((customer.latitude - center.latitude), 2) +
          pow((customer.longitude - center.longitude), 2))
    return difference < range
  }
}

Этот код определяет метод DeliveryRange(_:), который в настоящее время является членом DeliveryRange. В Swift, методы - это просто функции, которые связаны с типом. Так же, как и другие члены структур, вы можете использовать точечный синтаксис, чтобы получить доступ к методу через значения своего связанного типа:

let range = DeliveryRange(range: 150, center: Location(coordinateString: "44.9871,-93.2758"))
let customer = Location(coordinateString: "44.9850,-93.2750")
 
range.isInRange(customer) // true!

Структуры как значения

Термин "значение" имеет важное значение, когда дело доходит до структур в Swift, потому, что структуры создают то, что известно как "типы значений".

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

// Присвоим литерал ‘5’ в переменную a типа Int.
var a: Int = 5
 
// Присвоим значение a в b.
var b: Int = a
 
print(a) // 5
print(b) // 5
 
// Присвоим значение ’10’ в a
a = 10
 
// теперь a имеет значение ’10’, но b все еще имеет значение ‘5’
print(a) // 10
print(b) // 5

Просто, не так ли! И даже очевидное?

Обратите внимание, что даже если a и b имели одно и тоже значение с начала, то меняя значение a позднее, это не повлияет на b. Т.к. это отдельные переменные с отдельными значениями.

Как насчет того же принципа, кроме структуры Location:

// Создадим значение типа DeliveryRange
var range1: DeliveryRange = DeliveryRange(range: 200, center: Location(“44.9871,-93.2758”))
 
// Присвоим значения range1 в range2
var range2: DeliveryRange = range1
 
print(range1.range) // 200
print(range2.range) // 200
 
// Изменим значение range1 на ‘100’
range1.range = 100
 
// теперь range1 имеет значение ’100’, range2 все еще имеет значение ‘200’
print(range1.range) // 100
print(range2.range) // 200

Как и в примере с Int, range2 не подбирает новое значение, установленное в range1. Важно то, что это демонстрирует семантику значения работы со структурами. Когда вы назначаете range2 значение из range1, он получает точную копию значения. Это означает, что вы можете изменить range1 без изменения также диапазона range2.

Урок подготовлен командой SwiftBook.ru

За основу взят урок: http://www.raywenderlich.com/116714/swift-tutorial-introducing-structures

Содержание