Індекси
Класи, структури та перечислення можуть визначати індекси, що є скороченим синтаксисом для доступу до елементів колекції, списку чи послідовності. Індекси використовуються для присвоєння чи отримання значення за індексом без потреби у створенні окремих методів для присвоєння чи отримання. Наприклад, коли ви звертаєтесь до елементів масиву Array
як someArray[index]
, або до елементу в словнику Dictionary
як someDictionary[key]
, ви користуєтесь індексами.
Можна створювати кілька різних індексів для одного типу, і потрібна версія індексу буди вибрана, базуючись на типі значення, що передається в індекс. Індекси не обмежені одним виміром, можна створювати індекси, що приймають декілька вхідних параметрів, щоб відповідати потребам вашого власного типу.
Синтаксис індексації
Індекси дозволяють робити запити до екземплярів типу, записуючи одне чи більше значень у квадратних дужках після імені екземпляру. Їх синтаксис подібний одночасно до синтаксису методів екземпляру та властивостей, що обчислюються. Оголошення індексу починається із ключового слова subscript
, після чого вказуються один чи кілька вхідних параметрів та тип, що повертається, так само, як і в методах екземпляру. На відміну від методів екземпляру, індекси бувають двох видів: для читання й запису, та тільки для читання. Така поведінка передається гетером і сетером, аналогічно до властивостей, що обчислюються:
subscript(index: Int) -> Int {
get {
// відповідне значення індексу повертаєтсья тут
}
set(newValue) {
// присвоєння нового значення відбувається тут
}
}
Тип параметра newValue
співпадає із типом значення, що повертає індекс. Як і у властивостях, що обчислюються, можна не вказувати параметр сетера (newValue)
. В такому разі до сетера неявно передаватиметься параметр з іменем за замовчанням newValue
.
Як і з властивостями тільки для читання, що обчислюються, можна пропустити ключове слово get
:
subscript(index: Int) -> Int {
// return an appropriate subscript value here
}
Ось приклад реалізації індексу тільки для читання, що дозволяє структурі TimesTable
представляти таблицю множення на довільне ціле число:
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("шість помножити на три дорівнює \(threeTimesTable[6])")
// Надрукує "шість помножити на три дорівнює 18"
У даному прикладі створюється новий екземпляр структури TimesTable
для представлення таблиці множення на три. Це задається передачею значення 3
до ініціалізатора структури TimesTable
як значення для ініціалізації властивості multiplier
.
Тепер можна робити запити до екземпляра threeTimesTable
шляхом звернення до індексу, як показано у виклику threeTimesTable[6]
. У цьому виклику запитується шостий рядок у таблиці множення на три, що повертає значення 18
, або 3
помножити на 6
.
Примітка
Таблиця множення на ціле число базується на фіксованому математичному правилі, тому недоречно присвоювати
threeTimesTable[someIndex]
нове значення, і тому даний індекс вTimesTable
було оголошено як індекс тільки для читання.
Використання індексів
Точний зміст “індексу” залежить від контексту, в якому його використовують. Індекси, як правило, застосовуються як скорочений синтаксис для доступу до елементів у колекції, списку чи послідовності. Ви можете реалізовувати індекси у найбільш властивий для функціональності кожного конкретного класу чи структури спосіб.
Наприклад, тип Dictionary
у Swift реалізовує індекс для присвоєння та доступу до значень, що зберігаються в екземплярі Dictionary
. Можна задати значення у словнику, вказавши ключ відповідного типу у квадратних дужках індексу, та присвоївши значення відповідного типу:
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2
У прикладі вище визначено змінну на ім’я numberOfLegs
, яку ініціалізовано літералом словника, що містить три пари ключ-значення. Тип словника numberOfLegs
визначено компілятором як [String: Int]
. Після створення словника, у прикладі використовується індекс для додавання до словника ключа "bird"
типу String
та відповідного йому значення 2
типу Int
.
Детальнішу інформацію про індексацію словників можна знайти у підрозділі Доступ до елементів словника та його модифікація.
Примітка
У Swift тип
Dictionary
реалізовує індексацію ключ-значення за допомогою індексу, що приймає та повертає опціональний тип. У словникуnumberOfLegs
вище, індекс приймає та повертає значення типуInt?
, або “опціональний int”. ТипDictionary
використовує опціональний тип індекса для моделювання факту, що не кожен ключ має значення, і для можливості видаляти значення для заданого ключа шляхом присвоєння значенняnil
для цього ключа.
Опції індексів
Індекси можуть мати будь-яку кількість вхідних параметрів, і ці вхідні параметри можуть мати будь-який тип. Індекси також можуть повертати будь-який тип.
Як і функції, індекси можуть мати варіативні параметри, а параметри можуть мати значення за замовчанням, як описано у відповідних розділах Варіативні функції та Значення параметрів за замовчуванням. Однак, на відміну від функцій, індекси не можуть мати двонаправлених параметрів.
Клас чи структура може містити стільки реалізації індекси, скільки йому або їй потрібно; при цьому потрібна реалізація визначиться компілятором за типом значення чи значень, що містяться у квадратних дужках індексу в момент використання індексу. Ця можливість існування кількох індексів називається перевантаженням індексів.
Хоча найчастіше індекси приймають єдиний параметр, можна створювати індекси із кількома параметрами, якщо це підходить для даного типу. У наступному прикладі оголошено структуру Matrix
, що представляє двовимірну матрицю значень типу Double
. Індекс структури Matrix
приймає два цілочисельних параметри:
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(repeating: 0.0, count: rows * columns)
}
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
subscript(row: Int, column: Int) -> Double {
get {
assert(indexIsValid(row: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
Структура Matrix
містить ініціалізатор, що приймає два параметри rows
та columns
(кількість рядків та стовпчиків відповідно), і створює масив, достатньо великий для зберігання rows * columns
значень типу Double
. Кожна позиція в матриці отримує початкове значення 0.0
. Щоб досягнути цього, розмір масиву та початкове значення 0.0
передаються в ініціалізатор масиву, котрий створює та ініціалізує новий масив відповідного розміру. Цей ініціалізатор детальніше описано в підрозділі Створення масиву зі значенням за замовчанням.
Тепер можна створити новий екземпляр структури Matrix
, передавши потрібну кількість рядків та стовпчиків в її ініціалізатор:
var matrix = Matrix(rows: 2, columns: 2)
У попередньому прикладі створено новий екземпляр структури Matrix
із двома рядочками та двома стовпчиками. Масив grid
у цьому екземплярі фактично містить пласку розгортку матриці, ніби її вміст було прочитано зверху вниз зліва направо:
Значення у матриці можна задати, передаючи рядок та стовпчик до індексу, розділивши їх комою:

matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
Ці дві інструкції викликають сетер індексу, і задають значення 1.5
у верхній правій позиції матриці (де row
дорівнює 0
та column
дорівнює 1
), та значення 3.2
у нижній лівій позиції (де row
дорівнює 1
та column
дорівнює 0
):
Сетер та гетер індексу структури Matrix
містять assert
, котрий перевіряє, що передані параметри row
та column
є коректними. Щоб спростити ці перевірки assert
, структура Matrix
містить метод для зручності, що називається indexIsValid(row:column:)
. Цей метод перевіряє, чи є задані row
та column
в межах розмірів матриці:
func indexIsValidForRow(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
Якщо спробувати звернутись до індексу за межами розмірів матриці, спрацює assert
:
let someValue = matrix[2, 2]
// Цей код спровокує assert, оскільки [2, 2] лежить за межами розмірів матриці.
Індекси типів
Описані вище індекси є індексами екземплярів, тобто індекси, які можна викликати на конкретному екземплярі певного типу. На додачу до них, можна оголошувати індекси, які можна викликати на самому типі. Цей різновид індексів називається індексом типу. Індекси типів оголошуються за допомогою ключового слова static
перед ключовим словом subscript
. У класах можна також використовувати ключове слово class
замість static
: це дозволить нащадкам класу заміщувати реалізацію індексу батьківського класу. Приклад нижче демонструє визначення та звернення до індексу типу:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
static subscript(n: Int) -> Planet {
return Planet(rawValue: n)!
}
}
let mars = Planet[4]
print(mars)