ProgramaciónDesarrollador iOS Medio/Senior

¿Qué son los subscripts en Swift y cómo se pueden sobreescribir o extender? Dé un ejemplo de uso avanzado.

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

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.

Historia de la pregunta

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.

Problema

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.

Solución

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 pueden definir varios subscripts con diferentes tipos y cantidades de parámetros.
  • Un subscript puede ser solo get, o bien get/set.
  • Se puede usar un subscript con diferentes tipos de índices, incluso con rangos y cadenas.

Preguntas con trampa.

¿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 } } }

Errores comunes y anti-patrones

  • Realizar un acceso no seguro sin verificaciones de límites (precondition, bounds check)
  • Errores de tipo de datos para los parámetros del subscript.
  • Uso excesivo de subscripts sin necesidad, donde sería mejor usar métodos.

Ejemplo de la vida real

Caso negativo

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.

Caso positivo

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.