Subscripts в Swift обеспечивают удобный синтаксис для доступа к элементам коллекции, контейнера или пользовательского типа при помощи индексов, как в массивах и словарях. Их можно объявлять и переопределять в собственных классах, структурах и перечислениях.
Subscripts появились в Swift как способ унифицировать индексированный доступ к объектам, начав с коллекций, затем распространившись на любые пользовательские типы. Ключевая задача — повысить выразительность и читаемость, превратить методы доступа к данным в "натуральный" синтаксис, напоминающий работу с массивом.
В других языках (например, Java) доступ к элементу структуры осуществляется через методы get/set, что делает код громоздким. Стремление к лучшей читаемости и облегчению доступа к данным породило субскрипты. В Swift требуется особое внимание к безопасному доступу, геттеру и сеттеру, возможным ошибкам и возвращаемым значениям optional.
Subscript можно объявить в любом классе, структуре или перечислении посредством ключевого слова subscript. Дополнительно поддерживается несколько overload по параметрам и возвращаемым типам.
Пример пользовательского subscript:
struct Matrix { let rows: Int, columns: Int private var grid: [Double] init(rows: Int, columns: Int) { self.rows = rows self.columns = columns grid = Array(repeating: 0.0, count: rows * columns) } subscript(row: Int, column: Int) -> Double { get { precondition(row >= 0 && row < rows) precondition(column >= 0 && column < columns) return grid[(row * columns) + column] } set { precondition(row >= 0 && row < rows) precondition(column >= 0 && column < columns) grid[(row * columns) + column] = newValue } } } var matrix = Matrix(rows: 2, columns: 2) matrix[1, 1] = 3.14 print(matrix[1, 1]) // 3.14
Ключевые особенности:
Можно ли объявить subscript только с геттером, без сеттера?
Да. Если subscript нужен только для чтения, достаточно реализовать только get.
struct ReadOnlyArray { private let items = [1, 2, 3] subscript(index: Int) -> Int { get { return items[index] } } }
Разрешается ли в одном типе объявлять несколько subscript с разной сигнатурой?
Да, subscript могут быть перегружены (overload) по числу и типу параметров.
struct Collection { subscript(index: Int) -> String { ... } subscript(key: String) -> Int? { ... } }
Может ли subscript быть объявлен в протоколе и реализован в структуре/классе?
Да, протоколы могут требовать реализации subscript, и конкретные типы обязаны реализовать их.
protocol MySubscriptable { subscript(index: Int) -> String { get set } } struct SubData: MySubscriptable { private var items = ["a", "b"] subscript(index: Int) -> String { get { return items[index] } set { items[index] = newValue } } }
Разработчик реализовал subscript без проверки границ. В результате при обращении к несуществующему индексу приложение падало с ошибкой времени выполнения.
Плюсы: Код стал лаконичнее.
Минусы: Приложение стало аварийно завершаться из-за внезапных ошибок.
Реализация subscript с precondition и возможностью возвращения optional или выбрасывания ошибок.
Плюсы: Код стал безопаснее для пользователя, ошибки можно обработать.
Минусы: Нужно заложить дополнительную логику обработки ошибок и предусмотреть поведение для невалидных индексов.