Los subscripts en Swift ofrecen una sintaxis conveniente para acceder a los elementos de una colección, contenedor o tipo personalizado mediante índices, como en arreglos y diccionarios. Se pueden declarar y sobreescribir en clases, estructuras y enumeraciones propias.
Los subscripts aparecieron en Swift como una forma de unificar el acceso indexado a objetos, comenzando por colecciones y luego extendiéndose a cualquier tipo personalizado. La clave es aumentar la expresividad y la legibilidad, convirtiendo los métodos de acceso a datos en una sintaxis "natural" que recuerda el trabajo con arreglos.
En otros lenguajes (por ejemplo, Java) el acceso a un elemento de la estructura se realiza a través de métodos get/set, lo que hace que el código sea engorroso. La búsqueda de una mejor legibilidad y el acceso simplificado a los datos dio lugar a los subscripts. En Swift, se requiere especial atención al acceso seguro, al getter y al setter, a los posibles errores y a los valores retornados opcionales.
Un subscript se puede declarar en cualquier clase, estructura o enumeración utilizando la palabra clave subscript. Adicionalmente, se admite la sobrecarga (overloading) por parámetros y tipos de retorno.
Ejemplo de un subscript personalizado:
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
Características clave:
¿Se puede declarar un subscript solo con getter, sin setter?
Sí. Si el subscript es solo para lectura, es suficiente implementar solo el get.
struct ReadOnlyArray { private let items = [1, 2, 3] subscript(index: Int) -> Int { get { return items[index] } } }
¿Es permitido declarar varios subscripts con diferente firma en un mismo tipo?
Sí, los subscripts pueden ser sobrecargados (overloaded) por número y tipo de parámetros.
struct Collection { subscript(index: Int) -> String { ... } subscript(key: String) -> Int? { ... } }
¿Puede un subscript ser declarado en un protocolo y ser implementado en una estructura/clase?
Sí, los protocolos pueden requerir la implementación de un subscript, y los tipos específicos están obligados a implementarlos.
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 } } }
El desarrollador implementó un subscript sin comprobación de límites. Como resultado, al intentar acceder a un índice inexistente, la aplicación se bloqueaba con un error en tiempo de ejecución.
Pros: El código se volvió más conciso.
Contras: La aplicación empezó a fallar debido a errores inesperados.
Implementación de un subscript con precondition y la posibilidad de devolver un valor opcional o lanzar errores.
Pros: El código se volvió más seguro para el usuario, los errores se pueden manejar.
Contras: Es necesario incluir lógica adicional para el manejo de errores y prever el comportamiento para índices no válidos.