Si tengo una matriz en Swift e intento acceder a un índice que está fuera de los límites, hay un error de tiempo de ejecución poco sorprendente:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
Sin embargo, habría pensado con todo el encadenamiento opcional y la seguridad que brinda Swift, sería trivial hacer algo como:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
En vez de:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
Pero este no es el caso: tengo que usar la if
instrucción ol ' para verificar y asegurar que el índice sea menor que str.count
.
Intenté agregar mi propia subscript()
implementación, pero no estoy seguro de cómo pasar la llamada a la implementación original o acceder a los elementos (basados en índices) sin usar la notación de subíndice:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Respuestas:
La respuesta de Alex tiene buenos consejos y solución para la pregunta, sin embargo, me topé con una mejor manera de implementar esta funcionalidad:
Swift 3.2 y más reciente
Swift 3.0 y 3.1
Gracias a Hamish por encontrar la solución para Swift 3 .
Swift 2
Ejemplo
fuente
safe:
nombre del parámetro incluido para garantizar la diferencia.return self.indices ~= index ? self[index] : nil;
Collection
tipos dondeIndices
están no contiguo PorSet
ejemplo, si tuviéramos acceso a un elemento establecido por index (SetIndex<Element>
), podemos encontrar excepciones de tiempo de ejecución para los índices que son>= startIndex
y< endIndex
, en cuyo caso, el subíndice seguro falla (ver, por ejemplo, este ejemplo artificial ).contains
método iterará a través de todos los índices, haciendo de este un O (n) Una mejor manera es usar el índice y contar para verificar los límites.return index >= startIndex && index < endIndex ? self[index] : nil
Collection
tipos tienenstartIndex
,endIndex
que sonComparable
. Por supuesto, esto no funcionará para algunas colecciones extrañas que, por ejemplo, no tienen índices en el medio, la soluciónindices
es más general.Si realmente quieres este comportamiento, huele como si quisieras un diccionario en lugar de una matriz. Los diccionarios regresan
nil
al acceder a las teclas que faltan, lo que tiene sentido porque es mucho más difícil saber si una clave está presente en un diccionario, ya que esas claves pueden ser cualquier cosa, donde en una matriz la clave debe estar en un rango de:0
acount
. Y es increíblemente común iterar sobre este rango, donde puede estar absolutamente seguro de tener un valor real en cada iteración de un bucle.Creo que la razón por la que no funciona de esta manera es una elección de diseño hecha por los desarrolladores de Swift. Toma tu ejemplo:
Si ya sabe que el índice existe, como lo hace en la mayoría de los casos en los que usa una matriz, este código es excelente. Sin embargo, si se accede a un subíndice podría volver
nil
entonces ha cambiado el tipo de retorno deArray
'ssubscript
método para ser un opcional. Esto cambia su código a:Lo que significa que necesitaría desenvolver un elemento opcional cada vez que recorriera una matriz o hiciera algo más con un índice conocido, solo porque rara vez podría acceder a un índice fuera de los límites. Los diseñadores de Swift optaron por un menor desenvolvimiento de los opcionales, a expensas de una excepción de tiempo de ejecución al acceder a índices fuera de los límites. Y un bloqueo es preferible a un error lógico causado por un error
nil
que no esperaba en sus datos en alguna parte.Y estoy de acuerdo con ellos. Por lo tanto, no cambiará la
Array
implementación predeterminada porque rompería todo el código que espera valores no opcionales de las matrices.En su lugar, podría subclase
Array
y anularsubscript
para devolver un opcional. O, más prácticamente, podría extenderArray
con un método sin subíndice que hace esto.Actualización de Swift 3
func get(index: Int) ->
T?
necesita ser reemplazado porfunc get(index: Int) ->
Element?
fuente
subscript()
a opcional: este fue el obstáculo principal que se enfrentó al anular el comportamiento predeterminado. (Realmente no pude hacerlo funcionar . ) Estaba evitando escribir unget()
método de extensión, que es la opción obvia en otros escenarios (categorías Obj-C, ¿alguien?) Peroget(
no es mucho más grande[
y lo hace claro que el comportamiento puede diferir de lo que otros desarrolladores pueden esperar del operador de subíndice Swift. ¡Gracias!T
ha cambiado su nombre aElement
. Solo un recordatorio amistoso :)nil
lugar de causar una excepción de un índice fuera de límites sería ambiguo. Como, por ejemploArray<String?>
, también podría devolver nulo como miembro válido de la colección, no podrá diferenciar entre esos dos casos. Si tiene su propio tipo de colección que sabe que nunca puede devolver unnil
valor, es decir, es contextual para la aplicación, entonces puede extender Swift para verificar los límites de seguridad como se responde en esta publicación.Para construir sobre la respuesta de Nikita Kukushkin, a veces es necesario asignar de forma segura a los índices de matriz, así como leerlos, es decir
Así que aquí hay una actualización de la respuesta de Nikita (Swift 3.2) que también permite escribir de forma segura en índices de matriz mutable, agregando el nombre de parámetro seguro:.
fuente
Válido en Swift 2
A pesar de que esto ya ha sido respondido muchas veces, me gustaría presentar una respuesta más acorde a la moda de la programación Swift, que en palabras de Crusty es: "Piensa
protocol
primero"• ¿Qué queremos hacer?
- Obtenga un Elemento de un
Array
índice dado solo cuando sea seguro, y de lonil
contrario• ¿En qué debería basar esta funcionalidad su implementación?
-
Array
subscript
ing• ¿De dónde obtiene esta función?
- Su definición de
struct Array
en elSwift
módulo lo tiene• ¿Nada más genérico / abstracto?
- Adopta lo
protocol CollectionType
que también lo garantiza• ¿Nada más genérico / abstracto?
- También adopta
protocol Indexable
...• Sí, suena como lo mejor que podemos hacer. ¿Podemos extenderlo para tener esta característica que queremos?
- Pero tenemos tipos muy limitados (no
Int
) y propiedades (nocount
) para trabajar ahora!• Será suficiente. El stdlib de Swift está hecho bastante bien;)
¹: no es cierto, pero da la idea
fuente
Collection
probablemente :)Aquí hay algunas pruebas que realicé para usted:
fuente
fuente
SafeIndex
es una clase y no una estructura?Swift 4
Una extensión para aquellos que prefieren una sintaxis más tradicional:
fuente
Encontré que el arreglo seguro get, set, insert, remove es muy útil. Prefiero iniciar sesión e ignorar los errores, ya que todo lo demás pronto se vuelve difícil de administrar. Código completo abajo
Pruebas
fuente
El uso de la extensión mencionada anteriormente devuelve nil si el índice sale del límite en cualquier momento.
resultado - nulo
fuente
Me doy cuenta de que esta es una vieja pregunta. Estoy usando Swift5.1 en este punto, ¿el OP era para Swift 1 o 2?
Necesitaba algo como esto hoy, pero no quería agregar una extensión a escala completa para un solo lugar y quería algo más funcional (¿más seguro para subprocesos?). Tampoco necesitaba proteger contra índices negativos, solo aquellos que podrían haber pasado el final de una matriz:
Para aquellos que discuten sobre Secuencias con cero, ¿qué hacen con el
first
ylast
propiedades que nulo cambio de colecciones vacías?Me gustó esto porque podía agarrar cosas existentes y usarlas para obtener el resultado que quería. También sé que dropFirst (n) no es una copia de colección completa, solo una porción. Y luego el comportamiento ya existente de primero se hace cargo de mí.
fuente
Creo que esta no es una buena idea. Parece preferible construir código sólido que no resulte en intentar aplicar índices fuera de los límites.
Tenga en cuenta que si dicho error falla silenciosamente (como lo sugiere su código anterior) al regresar
nil
es propenso a producir errores aún más complejos e intratables.Puede hacer su anulación de una manera similar a la que usó y simplemente escribir los subíndices a su manera. El único inconveniente es que el código existente no será compatible. Creo que encontrar un gancho para anular el genérico x [i] (también sin un preprocesador de texto como en C) será un desafío.
Lo más cerca que puedo pensar es
EDITAR : Esto realmente funciona. ¡¡Un trazador de líneas!!
fuente
if let
en este caso no hace que el programa sea más complejo, ni los errores más intratables. Simplemente condensa laif
verificación tradicional de límites de dos declaraciones y la búsqueda real en una declaración condensada de una sola línea. Hay casos (sobre todo de trabajo en una interfaz de usuario), donde es normal para un índice que se va fuera de límites, como pedir unaNSTableView
para elselectedRow
y sin una selección.countElements
o como el hizo con OPcount
, pero no en la forma en que define el idioma de escritura subíndices de matriz.He rellenado la matriz con
nil
s en mi caso de uso:Compruebe también la extensión del subíndice con
safe:
etiqueta de Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/fuente
La lista "Cambios comúnmente rechazados" para Swift contiene una mención de cambiar el acceso al subíndice de matriz para devolver un elemento opcional en lugar de bloquearse:
Por lo tanto, el acceso básico al subíndice no cambiará para devolver un opcional.
Sin embargo, el equipo / comunidad Swift parece estar abierto a agregar un nuevo patrón de acceso de retorno opcional a las matrices, ya sea a través de una función o subíndice.
Esto ha sido propuesto y discutido en el foro Swift Evolution aquí:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
En particular, Chris Lattner le dio a la idea un "+1":
Por lo tanto, esto puede ser posible fuera de la caja en alguna versión futura de Swift. Animaría a cualquiera que quiera que contribuya a ese hilo de Swift Evolution.
fuente
Para propagar por qué fallan las operaciones, los errores son mejores que los opcionales. Los subíndices no pueden arrojar errores, por lo que debe ser un método.
fuente
No estoy seguro de por qué nadie ha puesto una extensión que también tiene un setter para hacer crecer automáticamente la matriz
El uso es fácil y funciona a partir de Swift 5.1
Nota: Debe comprender el costo de rendimiento (tanto en tiempo / espacio) cuando crece una matriz en Swift, pero para pequeños problemas a veces solo necesita hacer que Swift deje de Swift en el pie
fuente
He hecho una extensión simple para array
funciona perfectamente como está diseñado
Ejemplo
fuente
Uso rápido de 5
terminó con pero realmente quería hacer en general como
pero como la colección es contextual, supongo que es adecuada.
fuente
Cuando solo necesita obtener valores de una matriz y no le importa una pequeña penalización de rendimiento (es decir, si su colección no es enorme), existe una alternativa basada en el diccionario que no implica (una muy genérica, para mi gusto) extensión de la colección:
fuente