Основы Swift / 12.2. Сабскрипты


Видео


Сабскрипт
Классы, структуры и перечисления могут определять сабскрипты, которые являются сокращения для доступа к элементам-членам коллекции, списка или последовательности. Вы можете использовать сабскрипты для установки и получения значений по их индексу без необходимости разделять методы для установки и извлечения. Например, Вы можете получить доступ к элементам в объекте типа Array как someArray[index] и элементы в словаре в качестве someDictionary[key].

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

Сабскрипты позволяют Вам запрашивать экземпляры типа, записывая одно или несколько значений в квадратных скобках после имени объекта. Их синтаксис схож и с синтаксисом методов объекта, и с синтаксисом вычисляемого свойства. Вы можете написать определения сабскрипта с помощью ключевого слова subscript и определить один или несколько входных параметров и возвращаемый тип в том же стиле, что методы объектов. В отличие от методов объекта сабскрипты могут быть доступны для чтения-записи или только-чтения. Это поведение обеспечивается геттером и сеттером в том же ключе, что и для вычисляемых свойств:
@{12.2\1}
subscript(index: Int) -> Int {
    get {
        // вернуть подходящее значение
    }
    set(newValue) {
        // выполнить подходящие действия по установке значения
    }
}
Тип newValue такой же, как и у возвращаемого значения сабскрипта. Как и в случае с вычисляемыми свойствами, Вы можете выбрать не указывать параметр (newValue). Параметр по-умолчанию с названием newValue предоставляется для Вашего сеттера, если Вы не предоставили его сами.
Сабскрипты только-для-чтения
Как и с вычисляемыми свойствами только-для-чтения, Вы можете упростить объявление сабскрипта только-для-чтения, убрав ключевое слово get и его фигурные скобки:
@{12.2\2}
subscript(index: Int) -> Int {
    // вернуть подходящее значение сабскрипта
}
@{12.2\3}
Здесь представлен пример реализации сабскрипта только-для-чтения, который определяет структуру TimeTable для представления n-размерной таблицы целых чисел:
struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// Печатает "six times three is 18"
В этом примере новый экземпляр объекта TimesTable создаётся для представления таблицы умножения на три. Это индициируется передачей значения 3 в конструктор структуры в качестве значения для его параметра multiplier.

Вы можете запросить объект threeTimesTable, вызвав его сабскрипт, как показано в вызове threeTimesTable[6]. Это запросит шестое вхождение в таблице умножения на три, что вернёт значение 18 или 3x6.

Таблица умножения на n основывается на закреплённом математическом правиле. Это неверно устанавливать threeTimesTable[someIndex] новое значение, так что сабскрипт для TimesTable задан в режиме только-для-чтения.
Использование сабскриптов
Точное значение "сабскрипта" основывается на контексте, в котором он используется. Сабскрипты обычно используются как коротки ссылки на доступ к элементам в коллекции, списке или последовательности. Вы вольны реализовывать сабскрипты в наиболее подходящем способе для Вашего класса или структуры.

Например, тип Dictionary в Swift реализует сабскрипт для установки и извлечения значений, хранимых в объекте Dictionary. Вы можете установить значение в словаре, предоставив ключ типа словарного ключа внутри скобок сабскрипта, и присвоив значение типа значения словаря сабскрипту:
@{12.2\4}
var numberOfLegs = ["паук": 8, "муравей": 6, "кот": 4]
numberOfLegs["птица"] = 2
Пример определяет переменную с названием numberOfLegs и инициализиурет его словарным литералом, содержащим три пары ключ-значение. Тип словаря numberOfLegs выведен в качестве [String: Int]. После создания словаря этот пример использует присвоение сабскрипту для добавления ключа "bird" типа String и целого значения 2 типа Int в словарь.
Опции сабскриптов
Сабскрипты могут принимать любое число входных параметров, и эти входные параметры могут быть любого типа. Сабскрипты могут возвращать любой тип. Сабскрипты могут использовать вариативные параметры, но они не могут использовать in-out параметры или предоставлять дефолтные значения параметров.

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

И хотя обычно сабскрипт принимает один параметр, Вы также можете определить сабскрипт с несколькими параметрами, если это подходит для Вашего типа. Следующий пример определяет структуру Matrix, которая представляет собой двухмерную матрицу значений типа Double. Сабскрипт структуры Matrix принимает два целочисленных параметра:
@{12.2\5\1}
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 передав верное значение количества строк и столбцов в его конструктор:
@{12.2\5\2}
var matrix = Matrix(rows: 2, columns: 2)
Предыдущий пример создаёт новый объект типа Matrix с двумя строками и двумя столбцами. Массив grid для этого объекта типа Matrix на самом деле сжатая версия матрицы, если читать с верхнего левого угла в нижний правый.
Рисунок 12.2.1
Задание значения в матрице
Значения в матрице могут быть установлены передачей значений строки и столбца в сабскрипт с разделением через запятую:
@{12.2\5\3}
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
Эти две инструкции вызывают сеттер санскрита для установки значения 1.5 в верхнем правой позиции матрицы (где row равен 0 и column равен 1) и 3.2 в нижней левой позиции (где row равен 1 и column равен 0).

Сеттер и геттер сабскрипта Matrix оба содержат ассерт для проверки того, что значения row и column для сабскрипта корректны. Для работы с этими ассертами/ Matrix включает удобный метод с названием indexIsValid(row:column:), который проверяет, находятся ли запрашиваемые row и column внутри границ матрицы.
Рисунок 12.2.2
@{12.2\5\4}
Оператор выполнения сработает, если Вы попыталась получить доступ к сабскрипту, который находится за пределами границ матрицы:
let someValue = matrix[2, 2]
// это вызовет срабатывание ассерта, так как [2, 2] находится за границами матрицы