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 non surprenante:
var str = ["Apple", "Banana", "Coconut"] str[0] // "Apple" str[3] // EXC_BAD_INSTRUCTION
Cependant, j’aurais pensé avec tout le chaînage et la sécurité optionnels que Swift apporte, il serait sortingvial 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’append ma propre implémentation subscript()
, mais je ne sais pas comment passer l’appel à l’implémentation d’origine ou 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? } }
La réponse d’Alex a de bons conseils et la solution à la question, mais je suis tombé sur une meilleure façon de mettre en œuvre cette fonctionnalité:
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 Collection where Indices.Iterator.Element == Index { /// Returns the element at the specified index iff 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 Swift 3 .
extension CollectionType { /// Returns the element at the specified index iff 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, cela sent que vous voulez un dictionnaire plutôt qu’un tableau. Les dictionnaires renvoient nil
lors de l’access 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, dans un tableau la clé doit être 0
pour 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 effectué par les développeurs Swift. Prenons votre exemple:
var fruits: [Ssortingng] = ["Apple", "Banana", "Coconut"] var str: Ssortingng = "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’access à un indice peut éventuellement retourner nil
vous avez changé pour retourner le type de méthode de l’ subscript
Array
afin qu’il soit facultatif. Cela change votre code pour:
var fruits: [Ssortingng] = ["Apple", "Banana", "Coconut"] var str: Ssortingng = "I ate a \( fruits[0]! )" // ^ Added
Ce qui signifie que vous devez déplier un optionnel chaque fois que vous parcourez un tableau, ou que vous faites autre chose avec un index connu, simplement parce que vous pouvez rarement accéder à un index hors limites. Les concepteurs de Swift ont opté pour moins de déballage des options, au désortingment d’une exception d’exécution lors de l’access aux index hors limites. Et un crash est préférable à une erreur logique provoquée par un nil
vous ne vous attendiez pas quelque part.
Et je suis d’accord avec eux. Ainsi, vous ne modifierez pas l’implémentation Array
par défaut, car vous casseriez tout le code qui attend des valeurs non optionnelles des tableaux.
Au lieu de cela, vous pouvez sous-classe Array
, et remplacer subscript
pour retourner un facultatif. Ou, plus concrètement, vous pouvez étendre Array
avec une méthode non-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 on a déjà répondu à de nombreuses questions, je voudrais présenter une réponse plus cohérente quant à la manière dont fonctionne la programmation Swift, qui, selon Crusty, est: “Pensez au protocol
premier”
• Que voulons-nous faire?
– Obtenir un élément d’un Array
avec un index uniquement s’il est sûr et nil
sinon
• Quelle devrait être la base de cette fonctionnalité sur son implémentation?
– Array
ing
• D’où vient cette fonctionnalité?
– Sa définition de struct Array
dans le module Swift
a
• Rien de plus générique / abstrait?
– Il adopte le protocol CollectionType
qui le garantit également
• Rien de plus générique / abstrait?
– Il adopte également le protocol Indexable
…
• Ouais, ça sonne comme le mieux que nous puissions faire. Pouvons-nous alors l’étendre pour avoir cette fonctionnalité que nous voulons?
– Mais nous avons des types (no Int
) et des propriétés (pas de count
) très limités avec lesquels travailler maintenant!
• Ce sera suffisant. Le stdlib de Swift se fait plutôt bien;)
extension Indexable { public subscript(safe safeIndex: Index) -> _Element? { return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil } }
¹: pas vrai, mais ça donne l’idée
if let index = array.checkIndexForSafety (index: Int)
let item = array[safeIndex: index]
if 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 } }
J’ai trouvé le tableau sécurisé get, set, insert, remove très utile. Je préfère enregistrer et ignorer les erreurs car tout le rest devient 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: 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?
Pour tirer parti de la réponse de Nikita Kukushkin, vous devez parfois atsortingbuer en toute sécurité des index de tableau et en lire, c’est-à-dire
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é sur des index de tableau modifiables, en ajoutant le nom du paramètre safe:.
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 } } } }
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 de l’indice avec safe:
label par Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new- la semaine/
Une extension pour ceux qui préfèrent une syntaxe plus traditionnelle:
extension Array where Element: Equatable { func object(at index: Int) -> Element? { return indices.contains(index) ? self[index] : nil } }
Je pense que ce n’est pas une bonne idée. Il semble préférable de créer un code solide qui n’entraîne pas l’application d’index hors limites.
Considérez que le fait d’avoir une telle erreur en mode silencieux (comme suggéré par votre code ci-dessus) en renvoyant nil
est susceptible de produire des erreurs encore plus complexes et plus difficiles à résoudre.
Vous pouvez faire votre remplacement de la même manière que vous avez utilisé et simplement écrire les indices à votre façon. Le seul inconvénient est que le code existant ne sera pas compatible. Je pense que trouver un hook pour remplacer le x [i] générique (également sans préprocesseur de texte comme dans C) sera difficile.
Le plus proche que je peux penser est
// comstack 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") }
Comment attraper une telle exception avec une indexation incorrecte:
extension Array { func lookup(index : UInt) throws -> Element { if Int(index) >= count { throw NSError( domain: "com.sadun", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey: "Array index out of bounds"] ) } return self[Int(index)] } }
Exemple:
do { try ["Apple", "Banana", "Coconut"].lookup(index: 3) } catch { print(error) }