Sagen wir, ich habe ein ziemlich komplexes Wörterbuch, wie dieses:
%Vor% Ich kann auf alle Felder mit if let ..
blocks zugreifen, die optional auf etwas wirken, mit dem ich beim Lesen arbeiten kann.
Allerdings schreibe ich gerade Komponententests, bei denen ich Wörterbücher auf verschiedene Arten selektiv unterbrechen muss.
Aber ich kann nicht elegant Tasten aus dem Wörterbuch entfernen.
Zum Beispiel möchte ich den Schlüssel "japan"
in einem Test entfernen, in der nächsten "lat"
sollte nil sein.
Hier ist meine aktuelle Implementierung zum Entfernen von "lat"
:
Sicher muss es einen eleganteren Weg geben?
Idealerweise würde ich einen Testhelfer schreiben, der eine KVC-Zeichenkette hat und eine Signatur wie folgt hat:
%Vor% Im Fall "lat"
würde ich es mit dictWithoutKeyPath("countries.japan.capital.lat")
aufrufen.
Wenn bei der Arbeit mit einem Index das Subskript get / set ist und die Variable änderbar ist, ist der gesamte Ausdruck änderbar. Aufgrund der Typumwandlung verliert der Ausdruck jedoch die Veränderlichkeit. (Es ist kein l-Wert mehr).
Der kürzeste Weg, dies zu lösen, besteht darin, einen Index zu erstellen, der get / set ist und die Konvertierung für Sie vornimmt.
%Vor%Jetzt können Sie Folgendes schreiben:
%Vor%Uns hat diese Frage so gut gefallen, dass wir beschlossen haben, eine (öffentliche) Swift Talk-Episode darüber zu machen: untypisierte Wörterbücher mutieren
Interessante Frage. Das Problem scheint zu sein, dass Swifts optionaler Verkettungsmechanismus, der normalerweise in der Lage ist, verschachtelte Wörterbücher zu mutieren, über die notwendigen Typumwandlungen von Any
nach [String:Any]
stolpert. Während der Zugriff auf ein verschachteltes Element nur unlesbar wird (wegen der Typumwandlung):
... ein verschachteltes Element mutieren funktioniert nicht einmal:
%Vor%Die Idee ist, das nicht typisierte Wörterbuch loszuwerden und es in eine stark typisierte Struktur umzuwandeln, in der jedes Element denselben Typ hat. Ich gebe zu, dass dies eine schwerfällige Lösung ist, aber es funktioniert am Ende ziemlich gut.
Eine Aufzählung mit zugeordneten Werten würde gut für unseren benutzerdefinierten Typ funktionieren, der das nicht typisierte Wörterbuch ersetzt:
%Vor%Die Aufzählung hat einen Fall für jeden erwarteten Elementtyp. Die drei Fälle decken Ihr Beispiel ab, aber es könnte leicht erweitert werden, um mehr Typen abzudecken.
Als nächstes definieren wir zwei Indizes, einen für den verschlüsselten Zugriff auf ein Wörterbuch (mit Strings) und einen für den indizierten Zugriff auf ein Array (mit ganzen Zahlen). Die Indizes überprüfen, ob self
ein .dict
oder .array
ist und wenn ja, geben Sie den Wert am angegebenen Schlüssel / Index zurück. Sie geben nil
zurück, wenn der Typ nicht übereinstimmt, z. wenn Sie versucht haben, auf einen Schlüssel eines .string
-Wertes zuzugreifen. Die Indizes haben auch Setter. Dies ist der Schlüssel, um verkettete Mutationen zu aktivieren:
Schließlich fügen wir einige Convenience-Initialisierer hinzu, um unseren Typ mit Dictionary-, Array- oder String-Literalen zu initialisieren. Diese sind nicht unbedingt notwendig, erleichtern aber die Arbeit mit dem Typ:
%Vor%Und hier ist das Beispiel:
%Vor% Ich möchte meine vorherige Antwort mit einer anderen Lösung weiterverfolgen. Dieser erweitert Swifts Dictionary
type um einen neuen Index, der einen Schlüsselpfad benötigt.
Ich führe zuerst einen neuen Typ namens KeyPath
ein, um einen Schlüsselpfad darzustellen. Es ist nicht unbedingt notwendig, aber es macht das Arbeiten mit Schlüsselpfaden viel einfacher, weil es uns die Logik des Splittens eines Schlüsselpfads in seine Komponenten umschließt.
Als nächstes erstelle ich ein Dummy-Protokoll mit dem Namen StringProtocol
, das wir später auf die Erweiterung Dictionary
beschränken müssen. Swift 3.0 unterstützt noch keine Erweiterungen für generische Typen, die einen generischen Parameter auf einen konkreten Typ einschränken (z. B. extension Dictionary where Key == String
). Unterstützung dafür ist für Swift 4.0 geplant, aber bis dahin brauchen wir diese kleine Umgehungsmöglichkeit:
Jetzt können wir die neuen Indizes schreiben. Die Implementierung für Getter und Setter ist ziemlich lang, aber sie sollten einfach sein: Wir durchlaufen den Schlüsselpfad von Anfang bis Ende und dann erhalten / setzen wir den Wert an dieser Position:
%Vor%Und so sieht es aus:
%Vor%Ich mag diese Lösung wirklich. Es ist ziemlich viel Code, aber du musst es nur einmal schreiben und ich denke es sieht sehr gut aus.
Sie könnten rekursive Methoden (Lesen / Schreiben) konstruieren, die Ihren angegebenen Schlüsselpfad aufrufen, indem Sie wiederholt versuchen, die (Unter-) Wörterbuchwerte in [Key: Any]
-Wörterbücher selbst zu konvertieren. Darüber hinaus erlauben Sie den öffentlichen Zugang zu diesen Methoden über eine neue subscript
.
Beachten Sie, dass Sie Foundation
möglicherweise explizit für den Zugriff auf die Methode components(separatedBy:)
von String
(überbrückt) importieren müssen.
Beispieleinrichtung
%Vor%Beispielverwendung:
%Vor%Beachten Sie, dass wenn ein zugefügter Schlüsselpfad für eine Zuweisung nicht existiert (unter Verwendung von Setter), dies nicht zur Konstruktion des äquivalenten verschachtelten Wörterbuchs führt, sondern einfach zu keiner Mutation des Wörterbuchs führt.
Übergeben Sie Ihr Wörterbuch an diese Funktion. Es gibt Ihnen ein flaches Wörterbuch zurück, ohne dass ein geschachteltes Diktat enthalten ist.
// SWIFT 3.0
%Vor%Tags und Links ios swift dictionary collections nsdictionary