Wenn ich ein Array in Swift habe und versuche, auf einen Index zuzugreifen, der außerhalb der Grenzen liegt, liegt ein nicht überraschender Laufzeitfehler vor:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
Ich hätte jedoch gedacht, dass es bei all der optionalen Verkettung und Sicherheit , die Swift mit sich bringt, trivial wäre, etwas zu tun wie:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
Anstatt:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
Dies ist jedoch nicht der Fall - ich muss die alte if
Anweisung verwenden, um zu überprüfen und sicherzustellen, dass der Index kleiner als ist str.count
.
Ich habe versucht, meine eigene subscript()
Implementierung hinzuzufügen , bin mir jedoch nicht sicher, wie ich den Aufruf an die ursprüngliche Implementierung weiterleiten oder auf die Elemente zugreifen soll (indexbasiert), ohne die tiefgestellte Notation zu verwenden:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Antworten:
Alex 'Antwort hat gute Ratschläge und Lösungen für die Frage, aber ich bin zufällig auf eine schönere Art gestoßen, diese Funktionalität zu implementieren:
Swift 3.2 und neuer
Swift 3.0 und 3.1
Dank an Hamish für die Entwicklung der Lösung für Swift 3 .
Swift 2
Beispiel
quelle
safe:
Parameternamen, um den Unterschied sicherzustellen.return self.indices ~= index ? self[index] : nil;
Collection
Typen, bei denen dies der FallIndices
ist nicht zusammenhängend. ZB muss fürSet
Fälle, wenn wir durch den Index (ein Set Element zugreifen warenSetIndex<Element>
), können wir in Laufzeitausnahmen für Indizes ausführen, sind>= startIndex
und< endIndex
, in diesem Fall der sichere Index (siehe zB versagt dieses erfundene Beispiel ).contains
Methode durchläuft alle Indizes und macht dies zu einem O (n). Ein besserer Weg ist es, den Index zu verwenden und zu zählen, um die Grenzen zu überprüfen.return index >= startIndex && index < endIndex ? self[index] : nil
Collection
Typen habenstartIndex
,endIndex
welche sindComparable
. Natürlich funktioniert dies nicht für einige seltsame Sammlungen, die zum Beispiel keine Indizes in der Mitte haben. Die Lösung mitindices
ist allgemeiner.Wenn Sie dieses Verhalten wirklich wollen, riecht es so, als wollten Sie ein Wörterbuch anstelle eines Arrays. Wörterbücher Rückkehr
nil
beim Zugriff auf Schlüssel fehlen, was Sinn macht , weil es viel schwieriger zu wissen , ist , wenn ein Schlüssel in einem Wörterbuch vorhanden ist , da diese Schlüssel können alles sein, wo in einem Array der Schlüssel muss in einem Bereich von:0
zucount
. Und es ist unglaublich üblich, über diesen Bereich zu iterieren, wobei Sie absolut sicher sein können, dass jede Iteration einer Schleife einen echten Wert hat.Ich denke, der Grund, warum es nicht so funktioniert, ist eine Designentscheidung der Swift-Entwickler. Nehmen Sie Ihr Beispiel:
Wenn Sie bereits wissen, dass der Index vorhanden ist, wie Sie es in den meisten Fällen tun, wenn Sie ein Array verwenden, ist dieser Code großartig. Wenn der Zugriff auf einen Index jedoch möglicherweise zurückkehren könnte
nil
, haben Sie den Rückgabetyp der Methode vonArray
'subscript
optional geändert . Dies ändert Ihren Code in:Dies bedeutet, dass Sie jedes Mal, wenn Sie ein Array durchlaufen oder eine andere Option mit einem bekannten Index ausführen, ein optionales Element auspacken müssen, nur weil Sie selten auf einen Index außerhalb der Grenzen zugreifen können. Die Swift-Designer entschieden sich für weniger Auspacken von Optionen auf Kosten einer Laufzeitausnahme beim Zugriff auf Indizes außerhalb der Grenzen. Und ein Absturz ist einem logischen Fehler vorzuziehen, der durch einen Fehler verursacht wird, den
nil
Sie in Ihren Daten nicht irgendwo erwartet haben.Und ich stimme ihnen zu. Sie werden also die Standardimplementierung nicht ändern
Array
, da Sie den gesamten Code beschädigen würden, der nicht optionale Werte von Arrays erwartet.Stattdessen können Sie eine Unterklasse
Array
erstellen und überschreibensubscript
, um eine Option zurückzugeben. Praktischerweise können Sie dies auchArray
mit einer nicht tiefgestellten Methode erweitern.Swift 3 Update
func get(index: Int) ->
T?
muss ersetzt werden durchfunc get(index: Int) ->
Element?
quelle
subscript()
in einen optionalen Typ - dies war die primäre Hürde beim Überschreiben des Standardverhaltens. (Ich konnte es überhaupt nicht zum Laufen bringen . ) Ich habe es vermieden, eineget()
Erweiterungsmethode zu schreiben , was in anderen Szenarien (Obj-C-Kategorien, irgendjemand?) Die offensichtliche Wahl ist, aberget(
nicht viel größer als[
und macht es Es ist klar, dass sich das Verhalten möglicherweise von dem unterscheidet, was andere Entwickler vom Swift-Indexoperator erwarten. Danke dir!T
wurde in umbenanntElement
. Nur eine freundliche Erinnerung :)nil
anstatt eine Ausnahme von einem Index außerhalb der Grenzen zu verursachen, nicht eindeutig wäre. Da zBArray<String?>
auch nil als gültiges Mitglied der Sammlung zurückgegeben werden könnte, könnten Sie zwischen diesen beiden Fällen nicht unterscheiden. Wenn Sie einen eigenen Sammlungstyp haben, von dem Sie wissen, dass er niemals einennil
Wert zurückgeben kann, auch bekannt als kontextbezogen zur Anwendung, können Sie Swift für die Überprüfung sicherer Grenzen erweitern, wie in diesem Beitrag beantwortet.Um auf der Antwort von Nikita Kukushkin aufzubauen, müssen Sie manchmal Array-Indizes sicher zuweisen und daraus lesen, d. H.
Hier ist also ein Update von Nikitas Antwort (Swift 3.2), mit dem Sie auch sicher in veränderbare Array-Indizes schreiben können, indem Sie den Namen safe: parameter hinzufügen.
quelle
Gültig in Swift 2
Obwohl dies bereits oft beantwortet wurde, möchte ich eine Antwort geben, die mehr der Mode der Swift-Programmierung entspricht, die in Crustys Worten¹ lautet: "Think
protocol
s first".• Was wollen wir machen?
- Holen Sie sich ein Element eines
Array
bestimmten Index nur, wenn es sicher ist, undnil
ansonsten.• Worauf sollte diese Funktionalität bei der Implementierung basieren?
-
Array
subscript
ing• Wo es diese Funktion von bekommt?
- Seine Definition
struct Array
imSwift
Modul hat es• Nichts allgemeiner / abstrakter?
- Es nimmt an,
protocol CollectionType
was es auch sicherstellt.• Nichts allgemeineres / abstrakteres?
- Es nimmt
protocol Indexable
auch an ...• Ja, klingt nach dem Besten, was wir tun können. Können wir es dann erweitern, um diese Funktion zu haben, die wir wollen?
- Aber wir haben sehr begrenzte Typen (nein
Int
) und Eigenschaften (neincount
) jetzt arbeiten!• Es wird genug sein. Swifts stdlib ist ziemlich gut gemacht;)
¹: nicht wahr, aber es gibt die Idee
quelle
Collection
wahrscheinlich anhalten sollten :)Hier sind einige Tests, die ich für Sie durchgeführt habe:
quelle
quelle
SafeIndex
es einen Grund für eine Klasse und keine Struktur?Swift 4
Eine Erweiterung für diejenigen, die eine traditionellere Syntax bevorzugen:
quelle
Ich fand sicheres Array abrufen, setzen, einfügen, entfernen sehr nützlich. Ich ziehe es vor, die Fehler zu protokollieren und zu ignorieren, da alles andere bald schwer zu verwalten ist. Vollständiger Code unten
Tests
quelle
Wenn Sie die oben erwähnte Erweiterung verwenden, geben Sie null zurück, wenn der Index zu irgendeinem Zeitpunkt nicht mehr gebunden ist.
Ergebnis - Null
quelle
Mir ist klar, dass dies eine alte Frage ist. Ich benutze Swift5.1 zu diesem Zeitpunkt, das OP war für Swift 1 oder 2?
Ich brauchte heute so etwas, aber ich wollte nicht nur für einen Ort eine vollständige Erweiterung hinzufügen und wollte etwas funktionaleres (threadsicherer?). Ich musste mich auch nicht vor negativen Indizes schützen, sondern nur vor solchen, die möglicherweise hinter dem Ende eines Arrays liegen:
Was tun Sie für diejenigen, die über Nullen diskutieren, mit den Eigenschaften
first
undlast
, die für leere Sammlungen Null zurückgeben?Ich mochte das, weil ich einfach an vorhandenen Sachen greifen und sie verwenden konnte, um das gewünschte Ergebnis zu erzielen. Ich weiß auch, dass dropFirst (n) keine ganze Sammlungskopie ist, sondern nur ein Slice. Und dann übernimmt für mich das bereits existierende Verhalten von erst.
quelle
Ich denke, das ist keine gute Idee. Es scheint vorzuziehen, soliden Code zu erstellen, der nicht dazu führt, dass versucht wird, Out-of-Bound-Indizes anzuwenden.
Bitte beachten Sie, dass ein stillschweigender Fehler eines solchen Fehlers (wie in Ihrem obigen Code vorgeschlagen) bei der Rückgabe
nil
dazu neigt, noch komplexere, unlösbarere Fehler zu erzeugen.Sie können Ihre Überschreibung auf eine ähnliche Weise durchführen, die Sie verwendet haben, und die Indizes einfach auf Ihre eigene Weise schreiben. Der einzige Nachteil ist, dass vorhandener Code nicht kompatibel ist. Ich denke, einen Hook zu finden, um das generische x [i] zu überschreiben (auch ohne Textpräprozessor wie in C), wird eine Herausforderung sein.
Das nächste, an das ich denken kann, ist
EDIT : Das funktioniert tatsächlich. Einzeiler!!
quelle
if let
in diesem Fall weder das Programm komplexer noch die Fehler unlösbarer. Es verdichtet einfach die traditionelleif
Überprüfung der Grenzen mit zwei Anweisungen und die tatsächliche Suche zu einer einzeiligen, komprimierten Anweisung. Es gibt Fälle (insbesondere in einem UI - Betrieb) , wo es normal ist , ein Index der Grenzen heraus, wie eine zu fragenNSTableView
für dieselectedRow
ohne Auswahl.countElements
oder wie beim OPcount
, nur nicht so, wie die Sprache das Schreiben von Array-Indizes definiert.Ich habe das Array
nil
in meinem Anwendungsfall mit s aufgefüllt :Überprüfen Sie auch die tiefgestellte Erweiterung mit dem
safe:
Etikett von Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/quelle
Die Liste "Häufig abgelehnte Änderungen" für Swift enthält eine Erwähnung der Änderung des Array-Indexzugriffs, um einen optionalen statt eines Absturzes zurückzugeben:
Der grundlegende Indexzugriff wird also nicht geändert, um ein optionales Element zurückzugeben.
Das Swift-Team / die Swift-Community scheint jedoch offen dafür zu sein , Arrays ein neues optional zurückgegebenes Zugriffsmuster hinzuzufügen , entweder über eine Funktion oder einen Index.
Dies wurde im Swift Evolution-Forum hier vorgeschlagen und diskutiert:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
Insbesondere Chris Lattner gab der Idee eine "+1":
Dies ist möglicherweise in einer zukünftigen Version von Swift sofort möglich. Ich würde jeden ermutigen, der möchte, dass es zu diesem Swift Evolution-Thread beiträgt.
quelle
Um zu verbreiten, warum Operationen fehlschlagen, sind Fehler besser als Optionen. Indizes können keine Fehler auslösen, daher muss es sich um eine Methode handeln.
quelle
Ich bin mir nicht sicher, warum niemand eine Erweiterung eingerichtet hat, die auch einen Setter hat, um das Array automatisch zu vergrößern
Die Verwendung ist einfach und funktioniert ab Swift 5.1
Hinweis: Sie sollten die Leistungskosten (sowohl zeitlich als auch räumlich) verstehen, wenn Sie ein Array schnell vergrößern. Bei kleinen Problemen müssen Sie jedoch manchmal nur Swift dazu bringen, Swifting selbst im Fuß zu stoppen
quelle
Ich habe eine einfache Erweiterung für Array gemacht
es funktioniert perfekt wie geplant
Beispiel
quelle
Schnelle 5 Verwendung
endete mit aber wollte eigentlich generell gerne machen
aber da die Sammlung kontextbezogen ist, denke ich, ist es richtig.
quelle
Wenn Sie müssen nur bekommen Werte aus einem Array und Sie nicht über eine kleine Leistungseinbuße ausmachen (dh , wenn Sie Ihre Sammlung ist nicht sehr groß), eine Wörterbuch-basierte Alternative, die nicht zu allgemein mit sich bringt (für meine Geschmack) Sammlungserweiterung:
quelle