ПрограммированиеМидл/Сеньор iOS-разработчик

Что такое subscripts в Swift и как их можно переопределить или расширять? Дайте пример продвинутого использования.

Проходите собеседования с ИИ помощником Hintsage

Ответ.

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, либо get/set
  • Можно использовать subscript с разными типами индексов, даже с диапазонами и строками

Вопросы с подвохом.

Можно ли объявить 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 } } }

Типовые ошибки и анти-паттерны

  • Выполнение небезопасного доступа без проверок границ (precondition, bounds check)
  • Ошибки типа данных для параметров subscript
  • Избыточное использование subscript без необходимости, где лучше использовать методы

Пример из жизни

Негативный кейс

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

Плюсы: Код стал лаконичнее.

Минусы: Приложение стало аварийно завершаться из-за внезапных ошибок.

Позитивный кейс

Реализация subscript с precondition и возможностью возвращения optional или выбрасывания ошибок.

Плюсы: Код стал безопаснее для пользователя, ошибки можно обработать.

Минусы: Нужно заложить дополнительную логику обработки ошибок и предусмотреть поведение для невалидных индексов.