Si j'ai un tableau dans Swift et que j'essaie d'accéder à un index qui est hors limites, il y a une erreur d'exécution imprévue:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
Cependant, j'aurais pensé avec tous les chaînages optionnels et sécurité 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'instruction ol 'if
pour vérifier et vérifier que l'index est inférieur à str.count
.
J'ai essayé d'ajouter ma propre implémentation subscript()
, mais je ne sais pas comment passer l'appel à l'implémentation d'origine ni accéder aux éléments (à base d'index) sans utiliser la notation en indice:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
La réponse d'Alex a de bons conseils et une bonne solution à la question. Cependant, je suis tombé par hasard sur un moyen plus agréable d'implémenter cette fonctionnalité:
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
extension Collection where Indices.Iterator.Element == Index {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Generator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Nous remercions Hamish d’avoir proposé la solution pour Swift .
extension CollectionType {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Generator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
let array = [1, 2, 3]
for index in -20...20 {
if let item = array[safe: index] {
print(item)
}
}
Si vous voulez vraiment ce comportement, ça sent comme si vous voulez un dictionnaire au lieu d'un tableau. Les dictionnaires renvoient 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 dans une plage de: _0
_ à count
. Et il est incroyablement courant d'itérer sur cette plage, où vous pouvez être absolument certain 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 effectué par les développeurs Swift. Prenez votre exemple:
_var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"
_
Si vous savez déjà que l'index existe, comme dans la plupart des cas où vous utilisez un tableau, ce code est génial. Cependant, si l'accès à un indice pouvait éventuellement renvoyer nil
, vous avez alors modifié le type de retour de Array
'subscript
méthode soit facultative. Cela change votre code à:
_var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
// ^ Added
_
Ce qui signifie que vous devez dérouler une option chaque fois que vous parcourez un tableau ou faites autre chose avec un index connu, simplement parce que vous pourriez rarement accéder à un index hors limites. Les concepteurs Swift ont opté pour une réduction moins importante du nombre d'options, au détriment d'une exception d'exécution, lors de l'accès à des index hors limites. Et un crash est préférable à une erreur de logique provoquée par un nil
auquel vous ne vous attendiez pas quelque part dans vos données.
Et je suis d'accord avec eux. Par conséquent, vous ne modifierez pas l'implémentation Array
par défaut, car vous risqueriez de casser tout le code qui attend des valeurs non facultatives des tableaux.
Au lieu de cela, vous pouvez sous-classer Array
et remplacer subscript
pour renvoyer une valeur facultative. Ou, plus concrètement, vous pouvez étendre Array
avec une méthode sans indice qui le fait.
_extension Array {
// Safely lookup an index that might be out of bounds,
// returning nil if it does not exist
func get(index: Int) -> T? {
if 0 <= index && index < count {
return self[index]
} else {
return nil
}
}
}
var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
print("I ate a \( fruit )")
// I ate a Banana
}
if let fruit = fruits.get(3) {
print("I ate a \( fruit )")
// never runs, get returned nil
}
_
func get(index: Int) ->
T?
doit être remplacé par func get(index: Int) ->
Element?
Même si cela a déjà été répondu à maintes reprises, je voudrais présenter une réponse plus conforme à la tendance actuelle de la programmation de Swift, qui dans les termes de Crusty¹ est: "Pensez protocol
s en premier "
• Que voulons-nous faire?
- Obtenir un élément d'un Array
donné un index uniquement lorsqu'il est sûr, et nil
sinon
• Sur quoi cette fonctionnalité devrait-elle être basée?
- Array
subscript
ing
• D'où vient cette fonctionnalité?
- Sa définition de struct Array
dans le module Swift
l'a
• Rien de plus générique/abstrait?
- Il adopte protocol CollectionType
qui l’assure également
• Rien de plus générique/abstrait?
- Il adopte protocol Indexable
aussi ...
• Oui, cela semble être le meilleur que nous puissions faire. Pouvons-nous ensuite l'étendre pour avoir cette fonctionnalité que nous voulons?
- Mais nous avons des types très limités (pas de Int
) et des propriétés (pas de count
) pour travailler maintenant!
• Ce sera suffisant. La stdlib de Swift est assez bien faite;)
extension Indexable {
public subscript(safe safeIndex: Index) -> _Element? {
return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
}
}
¹: pas vrai, mais ça donne une idée
si let index = array.checkIndexForSafety (index: Int)
let item = array[safeIndex: index]
si let index = array.checkIndexForSafety (index: Int)
array[safeIndex: safeIndex] = myObject
extension Array {
@warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {
if indices.contains(index) {
// wrap index number in object, so can ensure type safety
return SafeIndex(indexNumber: index)
} else {
return nil
}
}
subscript(index:SafeIndex) -> Element {
get {
return self[index.indexNumber]
}
set {
self[index.indexNumber] = newValue
}
}
// second version of same subscript, but with different method signature, allowing user to highlight using safe index
subscript(safeIndex index:SafeIndex) -> Element {
get {
return self[index.indexNumber]
}
set {
self[index.indexNumber] = newValue
}
}
}
public class SafeIndex {
var indexNumber:Int
init(indexNumber:Int){
self.indexNumber = indexNumber
}
}
Pour donner suite à la réponse de Nikita Kukushkin, vous devez parfois affecter en toute sécurité des index de tableaux ainsi que les lire, c.-à-d.
myArray[safe: badIndex] = newValue
Voici donc une mise à jour de la réponse de Nikita (Swift 3.2) qui permet également d’écrire en toute sécurité dans des index de tableaux mutables, en ajoutant le paramètre safe: nom du paramètre.
extension Collection {
/// Returns the element at the specified index iff it is within bounds, otherwise nil.
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[ index] : nil
}
}
extension MutableCollection {
subscript(safe index: Index) -> Element? {
get {
return indices.contains(index) ? self[ index] : nil
}
set(newValue) {
if let newValue = newValue, indices.contains(index) {
self[ index] = newValue
}
}
}
}
Une extension pour ceux qui préfèrent une syntaxe plus traditionnelle:
extension Array {
func item(at index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
extension Array {
subscript (safe index: Index) -> Element? {
return 0 <= index && index < count ? self[index] : nil
}
}
Voici quelques tests que j'ai courus pour vous:
let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int?
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?
J'ai trouvé un 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 vite difficile à gérer. Code complet ci-dessous
/**
Safe array get, set, insert and delete.
All action that would cause an error are ignored.
*/
extension Array {
/**
Removes element at index.
Action that would cause an error are ignored.
*/
mutating func remove(safeAt index: Index) {
guard index >= 0 && index < count else {
print("Index out of bounds while deleting item at index \(index) in \(self). This action is ignored.")
return
}
remove(at: index)
}
/**
Inserts element at index.
Action that would cause an error are ignored.
*/
mutating func insert(_ element: Element, safeAt index: Index) {
guard index >= 0 && index <= count else {
print("Index out of bounds while inserting item at index \(index) in \(self). This action is ignored")
return
}
insert(element, at: index)
}
/**
Safe get set subscript.
Action that would cause an error are ignored.
*/
subscript (safe index: Index) -> Element? {
get {
return indices.contains(index) ? self[index] : nil
}
set {
remove(safeAt: index)
if let element = newValue {
insert(element, safeAt: index)
}
}
}
}
Des tests
import XCTest
class SafeArrayTest: XCTestCase {
func testRemove_Successful() {
var array = [1, 2, 3]
array.remove(safeAt: 1)
XCTAssert(array == [1, 3])
}
func testRemove_Failure() {
var array = [1, 2, 3]
array.remove(safeAt: 3)
XCTAssert(array == [1, 2, 3])
}
func testInsert_Successful() {
var array = [1, 2, 3]
array.insert(4, safeAt: 1)
XCTAssert(array == [1, 4, 2, 3])
}
func testInsert_Successful_AtEnd() {
var array = [1, 2, 3]
array.insert(4, safeAt: 3)
XCTAssert(array == [1, 2, 3, 4])
}
func testInsert_Failure() {
var array = [1, 2, 3]
array.insert(4, safeAt: 5)
XCTAssert(array == [1, 2, 3])
}
func testGet_Successful() {
var array = [1, 2, 3]
let element = array[safe: 1]
XCTAssert(element == 2)
}
func testGet_Failure() {
var array = [1, 2, 3]
let element = array[safe: 4]
XCTAssert(element == nil)
}
func testSet_Successful() {
var array = [1, 2, 3]
array[safe: 1] = 4
XCTAssert(array == [1, 4, 3])
}
func testSet_Successful_AtEnd() {
var array = [1, 2, 3]
array[safe: 3] = 4
XCTAssert(array == [1, 2, 3, 4])
}
func testSet_Failure() {
var array = [1, 2, 3]
array[safe: 4] = 4
XCTAssert(array == [1, 2, 3])
}
}
extension Array {
subscript (safe index: UInt) -> Element? {
return Int(index) < count ? self[Int(index)] : nil
}
}
Utiliser l'extension mentionnée ci-dessus renvoie nil si l'index est dépassé à tout moment.
let fruits = ["Apple","banana"]
print("result-\(fruits[safe : 2])")
résultat - nul
Je pense que ce n'est pas une bonne idée. Il semble préférable de créer un code solide ne conduisant pas à appliquer des index hors limites.
Veuillez considérer qu’une telle erreur échoue de manière silencieuse (comme le suggère votre code ci-dessus) en renvoyant nil
risque de produire des erreurs encore plus complexes et plus difficiles à traiter.
Vous pouvez effectuer votre remplacement de la même manière que vous avez utilisé et écrire les indices à votre guise. 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 un défi.
Le plus proche que je peux penser est
// compile error:
if theIndex < str.count && let existing = str[theIndex]
EDIT: Cela fonctionne réellement. Bon mot!!
func ifInBounds(array: [AnyObject], idx: Int) -> AnyObject? {
return idx < array.count ? array[idx] : nil
}
if let x: AnyObject = ifInBounds(swiftarray, 3) {
println(x)
}
else {
println("Out of bounds")
}
J'ai rempli le tableau avec nil
s dans mon cas d'utilisation:
let components = [1, 2]
var nilComponents = components.map { $0 as Int? }
nilComponents += [nil, nil, nil]
switch (nilComponents[0], nilComponents[1], nilComponents[2]) {
case (_, _, .Some(5)):
// process last component with 5
default:
break
}
Vérifiez également l'extension avec l'indice safe:
par Erica Sadun/Mike Ash: http://ericasadun.com/2015/06/01/Swift-safe-array-indexing-my-favorite-thing -de-la-nouvelle-semaine /
J'ai fait une simple extension pour array
extension Array where Iterator.Element : AnyObject {
func iof (_ i : Int ) -> Iterator.Element? {
if self.count > i {
return self[i] as Iterator.Element
}
else {
return nil
}
}
}
cela fonctionne parfaitement comme prévu
Exemple
if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode,