Si j'ai un tableau dans Swift et que j'essaie d'accéder à un index hors limites, il y a une erreur d'exécution sans surprise:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
Cependant, j'aurais pensé qu'avec tout le chaînage et la sécurité optionnels que Swift apporte, il serait trivial de faire quelque chose comme:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
Au lieu de:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
Mais ce n'est pas le cas - je dois utiliser l' if
instruction ol ' pour vérifier et m'assurer que l'indice est inférieur à str.count
.
J'ai essayé d'ajouter ma propre subscript()
implémentation, mais je ne sais pas comment passer l'appel à l'implémentation d'origine, ou pour accéder aux éléments (basés sur un index) sans utiliser la notation en indice:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Réponses:
La réponse d'Alex contient de bons conseils et une bonne solution pour la question, cependant, je suis tombé sur une meilleure façon de mettre en œuvre cette fonctionnalité:
Swift 3.2 et plus récent
Swift 3.0 et 3.1
Nous remercions Hamish d'avoir trouvé la solution pour Swift 3 .
Swift 2
Exemple
la source
safe:
nom du paramètre inclus pour assurer la différence.return self.indices ~= index ? self[index] : nil;
Collection
types où lesIndices
sont pas contiguë. ParSet
exemple, si nous devions accéder à un élément set par index (SetIndex<Element>
), nous pouvons exécuter des exceptions d'exécution pour les index qui sont>= startIndex
et< endIndex
, dans ce cas, l'indice sécurisé échoue (voir par exemple cet exemple artificiel ).contains
méthode parcourra tous les indices, ce qui en fera un O (n). Une meilleure façon est d'utiliser l'index et le comptage pour vérifier les limites.return index >= startIndex && index < endIndex ? self[index] : nil
Collection
types ontstartIndex
,endIndex
qui le sontComparable
. Bien sûr, cela ne fonctionnera pas pour certaines collections étranges qui, par exemple, n'ont pas d'indices au milieu, la solution avecindices
est plus générale.Si vous voulez vraiment ce comportement, cela sent comme si vous vouliez un dictionnaire au lieu d'un tableau. Les dictionnaires reviennent
nil
lors de l'accès aux clés manquantes, ce qui est logique car il est beaucoup plus difficile de savoir si une clé est présente dans un dictionnaire car ces clés peuvent être n'importe quoi, où dans un tableau la clé doit être dans une plage de:0
àcount
. Et il est incroyablement courant d'itérer sur cette plage, où vous pouvez être absolument sûr d' avoir une valeur réelle à chaque itération d'une boucle.Je pense que la raison pour laquelle cela ne fonctionne pas de cette façon est un choix de conception fait par les développeurs de Swift. Prenez votre exemple:
Si vous savez déjà que l'index existe, comme vous le faites dans la plupart des cas où vous utilisez un tableau, ce code est parfait. Cependant, si l'accès à un indice peut éventuellement renvoyer,
nil
vous avez modifié le type de retour deArray
lasubscript
méthode de en option. Cela change votre code en:Ce qui signifie que vous devrez déballer une option à chaque fois que vous itérerez dans un tableau, ou que vous ferez quoi que ce soit d'autre avec un index connu, simplement parce que vous pourriez rarement accéder à un index hors limites. Les concepteurs de Swift ont opté pour moins de déballage des options, au détriment d'une exception d'exécution lors de l'accès aux index hors limites. Et un plantage est préférable à une erreur logique causée par un résultat
nil
inattendu quelque part dans vos données.Et je suis d'accord avec eux. Vous ne modifierez donc pas l'
Array
implémentation par défaut, car vous casseriez tout le code qui attend des valeurs non facultatives des tableaux.Au lieu de cela, vous pouvez sous
Array
-classer et remplacersubscript
pour renvoyer une option. Ou, plus concrètement, vous pouvez étendreArray
avec une méthode non-indice qui fait cela.Mise à jour Swift 3
func get(index: Int) ->
T?
doit être remplacé parfunc get(index: Int) ->
Element?
la source
subscript()
en option - c'était le principal obstacle rencontré pour remplacer le comportement par défaut. (Je ne pouvais pas vraiment le faire fonctionner du tout. ) J'évitais d'écrire uneget()
méthode d'extension, ce qui est le choix évident dans d'autres scénarios (catégories Obj-C, n'importe qui?) Maisget(
n'est pas beaucoup plus grand que[
, et le fait clairement que le comportement peut différer de ce que les autres développeurs peuvent attendre de l'opérateur d'indice Swift. Je vous remercie!T
a été renomméElement
. Juste un rappel amical :)nil
au lieu de provoquer une exception à partir d'un index hors limites serait ambigu. Comme par exemple,Array<String?>
pourrait également renvoyer nil en tant que membre valide de la collection, vous ne pourrez pas faire la différence entre ces deux cas. Si vous avez votre propre type de collection et que vous savez qu'il ne peut jamais renvoyer denil
valeur, c'est-à-dire qu'il est contextuel à l'application, vous pouvez étendre Swift pour une vérification des limites sécurisées, comme indiqué dans cet article.Pour s'appuyer sur la réponse de Nikita Kukushkin, vous devez parfois attribuer en toute sécurité des index de tableaux ainsi que les lire, c'est-à-dire
Voici donc une mise à jour de la réponse de Nikita (Swift 3.2) qui permet également d'écrire en toute sécurité dans les index de tableaux mutables, en ajoutant le nom du paramètre safe :.
la source
Valide dans Swift 2
Même si cela a déjà été répondu à maintes reprises, je voudrais présenter une réponse plus conforme à la tendance de la programmation Swift, qui, selon les mots de Crusty¹, est: "Pensez d'
protocol
abord"• Que voulons-nous faire?
- Obtenir un élément d'un
Array
index donné uniquement lorsqu'il est sûr, etnil
sinon• Sur quoi cette fonctionnalité doit-elle baser son implémentation?
-
Array
subscript
ing• D'où obtient-elle cette fonctionnalité?
- Sa définition
struct Array
dans leSwift
module l'a• Rien de plus générique / abstrait?
- Il adopte
protocol CollectionType
ce qui le garantit aussi• Rien de plus générique / abstrait?
- Il adopte
protocol Indexable
aussi ...• Ouaip, ça sonne comme le mieux qu'on puisse faire. Pouvons-nous alors l'étendre pour avoir cette fonctionnalité que nous voulons?
- Mais nous avons des types
Int
et des propriétés très limités (non )count
) pour travailler avec maintenant!• Ce sera suffisant. Le stdlib de Swift est plutôt bien fait;)
¹: pas vrai, mais ça donne l'idée
la source
Collection
probablement :)Voici quelques tests que j'ai effectués pour vous:
la source
la source
SafeIndex
est une classe et non une structure?Swift 4
Une extension pour ceux qui préfèrent une syntaxe plus traditionnelle:
la source
J'ai trouvé le tableau sécurisé get, set, insert, remove très utile. Je préfère me connecter et ignorer les erreurs car tout le reste devient rapidement difficile à gérer. Code complet ci-dessous
Les tests
la source
L'utilisation de l'extension mentionnée ci-dessus renvoie nil si à tout moment l'index sort des limites.
résultat - nul
la source
Je me rends compte que c'est une vieille question. J'utilise Swift5.1 à ce stade, l'OP était pour Swift 1 ou 2?
J'avais besoin de quelque chose comme ça aujourd'hui, mais je ne voulais pas ajouter une extension pleine échelle pour un seul endroit et je voulais quelque chose de plus fonctionnel (plus sûr pour les threads?). Je n'avais pas non plus besoin de me protéger contre les indices négatifs, juste ceux qui pourraient être au-delà de la fin d'un tableau:
Pour ceux qui discutent des séquences avec nil, que faites-vous des propriétés
first
etlast
qui renvoient nil pour les collections vides?J'ai aimé cela parce que je pouvais simplement saisir des choses existantes et les utiliser pour obtenir le résultat que je voulais. Je sais également que dropFirst (n) n'est pas une copie de collection entière, juste une tranche. Et puis le comportement déjà existant du premier prend le relais pour moi.
la source
Je pense que ce n'est pas une bonne idée. Il semble préférable de construire du code solide qui n'entraîne pas d'essayer d'appliquer des index hors limites.
Veuillez considérer qu'avoir une telle erreur échoue silencieusement (comme suggéré par votre code ci-dessus) en retournant
nil
est susceptible de produire des erreurs encore plus complexes et plus intraitables.Vous pouvez effectuer votre remplacement d'une manière similaire à celle que vous avez utilisée et simplement écrire les indices à votre manière. Le seul inconvénient est que le code existant ne sera pas compatible. Je pense que trouver un crochet pour remplacer le x [i] générique (également sans préprocesseur de texte comme en C) sera difficile.
Le plus proche auquel je peux penser est
EDIT : Cela fonctionne réellement. Bon mot!!
la source
if let
dans ce cas ne rend pas le programme plus complexe, ni les erreurs plus insolubles. Il condense simplement laif
vérification des limites à deux instructions traditionnelle et la recherche réelle en une instruction condensée à simple ligne. Il y a des cas (en particulier dans une interface utilisateur) où il est normal qu'un index soit hors limites, comme demander unNSTableView
pourselectedRow
sans sélection.countElements
ou comme l'OP l'a faitcount
, mais pas de la manière dont le langage définit l'écriture des indices de tableau.J'ai complété le tableau avec
nil
s dans mon cas d'utilisation:Vérifiez également l'extension d'indice avec l'
safe:
étiquette par Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/la source
La liste des "Changements fréquemment rejetés" pour Swift contient une mention concernant la modification de l' accès aux indices de tableau pour renvoyer une option plutôt que de planter:
Ainsi, l'accès en indice de base ne changera pas pour renvoyer une option.
Cependant, l'équipe / communauté Swift semble ouverte à l' ajout d' un nouveau modèle d'accès à retour facultatif aux tableaux, via une fonction ou un indice.
Ceci a été proposé et discuté sur le forum Swift Evolution ici:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
Notamment, Chris Lattner a donné à l'idée un "+1":
Cela peut donc être possible dès la sortie de la boîte dans une future version de Swift. J'encourage tous ceux qui le souhaitent à contribuer à ce fil Swift Evolution.
la source
Pour propager pourquoi les opérations échouent, les erreurs sont meilleures que les options. Les abonnements ne peuvent pas générer d'erreurs, il faut donc que ce soit une méthode.
la source
Je ne sais pas pourquoi personne n'a mis en place une extension qui dispose également d'un setter pour agrandir automatiquement le tableau
L'utilisation est facile et fonctionne à partir de Swift 5.1
Remarque: vous devez comprendre le coût des performances (à la fois dans le temps et dans l'espace) lors de la croissance d'un tableau dans swift - mais pour les petits problèmes, il vous suffit parfois de faire en sorte que Swift arrête de se déplacer rapidement dans le pied
la source
J'ai fait une extension simple pour le tableau
il fonctionne parfaitement comme prévu
Exemple
la source
Utilisation de Swift 5
fini avec mais voulait vraiment faire généralement comme
mais comme la collection est contextuelle, je suppose qu'elle est appropriée.
la source
Lorsque vous avez seulement besoin d' obtenir des valeurs d'un tableau et que cela ne vous dérange pas une petite pénalité de performance (c'est-à-dire si votre collection n'est pas énorme), il existe une alternative basée sur le dictionnaire qui n'implique pas (trop générique, pour mon goût) extension de collection:
la source