ПрограммированиеiOS разработчик

Что такое subscripting в Swift и как реализовать кастомный subscript для собственной структуры?

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

Ответ.

История вопроса:

Subscript — это механизм обращения к значениям внутри коллекций, структур и других типов с помощью квадратных скобок. Он добавлен в Swift по аналогии с другими языками (например, индексация в массивах Python, оператор [] в C++/Java), чтобы сделать работу с коллекциями и подобными объектами более интуитивной.

Проблема:

Не все типы по умолчанию поддерживают обращение по индексу как массив. Когда у нас есть структура или класс с данными, доступ к элементам часто логичнее реализовать через привычный синтаксис (типа myObject[index]), а не методом.

Решение:

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

struct Matrix { let size: Int private var grid: [Int] init(size: Int) { self.size = size self.grid = Array(repeating: 0, count: size * size) } subscript(row: Int, column: Int) -> Int { get { precondition(indexIsValid(row, column), "Index out of range") return grid[(row * size) + column] } set { precondition(indexIsValid(row, column), "Index out of range") grid[(row * size) + column] = newValue } } private func indexIsValid(_ row: Int, _ column: Int) -> Bool { return row >= 0 && row < size && column >= 0 && column < size } } var m = Matrix(size: 3) m[1,2] = 7 print(m[1,2]) // 7

Ключевые особенности:

  • Subscript может принимать любое количество аргументов, не только один индекс
  • Реализуется с помощью ключевого слова subscript и может быть get-only либо get/set
  • Позволяет создавать удобный и читаемый API для пользовательских коллекций или структур

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

Subscript могут использоваться только для коллекций?

Нет, subscript могут быть реализованы для любых типов: структура, класс, enum. Тип данных внутри не должен быть именно коллекцией, главное — логика обработки запросов через subscript.

Можно ли сделать несколько subscript с разной сигнатурой в одном типе?

Да, разрешено перегружать subscript с разной сигнатурой параметров:

struct Example { subscript(index: Int) -> Int { return index } subscript(key: String) -> String { return key.uppercased() } }

Допускается ли использовать subscript как computed property без set?

Да, subscript может быть только для чтения (get-only), тогда попытка записи вызовет ошибку компиляции.

struct ReadOnly { subscript(index: Int) -> Int { index * index } }

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

  • Отсутствие проверки диапазона индексов (Index out of range)
  • Нарушение принципа “single responsibility” — subscript содержит бизнес-логику, а не только доступ к данным
  • Слишком сложные и неочевидные сигнатуры subscript

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

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

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

Плюсы:

  • Быстрый прототип
  • Меньше кода

Минусы:

  • Высокая вероятность runtime-crash
  • Трудности в отладке из-за неочевидной ошибки

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

Matrix реализован с precondition на диапазон индексов. Любая ошибка отлавливается сразу, не попадает в production.

Плюсы:

  • Безопасность работы с инпутами
  • Легкая диагностика и поддержка

Минусы:

  • Чуть больше кода и проверок, но надежность выше