Derzeit ist die überwiegende Mehrheit der mobilen Anwendungen Client-Server. Überall wird geladen, synchronisiert, Ereignisse gesendet und die Hauptmethode für die Interaktion mit dem Server besteht darin, Daten im JSON-Format auszutauschen.
Schlüsseldecodierung
Die Stiftung verfügt über zwei Mechanismen zur Serialisierung von toten Daten. Alt ist NSJsonSerialization
und neu ist Codable
. Der letzte in der Liste der Vorteile enthält eine wunderbare Funktion wie die automatische Generierung von Schlüsseln für JSON-Daten auf der Grundlage einer Struktur (oder Klasse), die Codable
( Encodable
, Decodable
) und einen Initialisierer zum Decodieren von Daten implementiert.
Und alles scheint in Ordnung zu sein, man kann es benutzen und genießen, aber die Realität ist nicht so einfach.
Sehr oft sieht man auf dem Server json des Formulars:
{"topLevelObject": { "underlyingObject": 1 }, "Error": { "ErrorCode": 400, "ErrorDescription": "SomeDescription" } }
Dies ist ein fast reales Beispiel von einem der Projektserver.
Für die JsonDecoder
Klasse können JsonDecoder
die Arbeit mit snake_case-Schlüsseln angeben. Was ist jedoch, wenn wir UpperCamelCase, Dash-Snake-Case oder sogar ein allgemeines Durcheinander haben, die Schlüssel jedoch nicht manuell schreiben möchten?
Glücklicherweise bot Apple die Möglichkeit, die Schlüsselzuordnung zu konfigurieren, bevor sie mit CodingKeys
mit der CodingKeys
Struktur JSONDecoder.KeyDecodingStrategy
. Wir werden dies nutzen.
Erstellen Sie zunächst eine Struktur, die das CodingKey
Protokoll implementiert, da die Standardbibliothek keine enthält:
struct AnyCodingKey: CodingKey { var stringValue: String var intValue: Int? init(_ base: CodingKey) { self.init(stringValue: base.stringValue, intValue: base.intValue) } init(stringValue: String) { self.stringValue = stringValue } init(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } init(stringValue: String, intValue: Int?) { self.stringValue = stringValue self.intValue = intValue } }
Dann ist es notwendig, jeden Fall unserer Schlüssel separat zu bearbeiten. Die wichtigsten:
snake_case, dash-snake-case, lowerCamelCase und UpperCamelCase. Überprüfen, ausführen, alles funktioniert.
Dann stoßen wir auf ein eher erwartetes Problem: Abkürzungen in camelCase'ah (erinnern Sie sich an die zahlreichen ID
, ID
, ID
). Damit es funktioniert, müssen Sie sie korrekt konvertieren und eine Regel einführen. Abkürzungen werden in camelCase konvertiert, wobei nur der Großbuchstabe beibehalten wird und myABBRKey in myAbbrKey umgewandelt wird .
Diese Lösung eignet sich hervorragend für Kombinationen aus mehreren Fällen.
Hinweis: Die Implementierung erfolgt in. .custom
Schlüsseldecodierungsstrategie.
static func convertToProperLowerCamelCase(keys: [CodingKey]) -> CodingKey { guard let last = keys.last else { assertionFailure() return AnyCodingKey(stringValue: "") } if let fromUpper = convertFromUpperCamelCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromUpper) } else if let fromSnake = convertFromSnakeCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromSnake) } else { return AnyCodingKey(last) } }
Datumsdecodierung
Das nächste Routineproblem ist die Art und Weise, wie Daten übergeben werden. Es gibt viele Microservices auf dem Server, es gibt etwas weniger Teams, aber auch eine anständige Menge, und am Ende stehen wir vor einer Reihe von Datumsformaten wie „Ja, ich verwende den Standard“. Außerdem übergibt jemand Daten in einer Zeichenfolge, jemand in der Epochenzeit. Infolgedessen gibt es wieder ein Durcheinander von Kombinationen aus Zeichenfolge, Zeitzone, Millisekunde und Trennzeichen, und DateDecoder
in iOS beschwert sich und erfordert ein striktes Datumsformat. Die Lösung hier ist einfach: Durch Suchen suchen wir nach Zeichen eines bestimmten Formats und kombinieren sie, um das erforderliche Ergebnis zu erzielen. Diese Formate haben meine Fälle erfolgreich und vollständig abgedeckt.
Hinweis: Dies ist ein benutzerdefinierter DateFormatter-Initialisierer. Das Format ist nur auf den erstellten Formatierer eingestellt.
static let onlyDate = DateFormatter(format: "yyyy-MM-dd") static let full = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSSx") static let noWMS = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ssZ") static let noWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSS") static let noWMSnoWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss")
Wir hängen dies mit JSONDecoder.DateDecodingStrategy
an unseren Decoder JSONDecoder.DateDecodingStrategy
und erhalten einen Decoder, der fast alles verarbeitet und in ein für uns verdauliches Format konvertiert.
Leistungstests
Es wurden Tests für JSON-Zeichenfolgen mit einer Größe von 7944 Byte durchgeführt.
Wie wir sehen können, ist der benutzerdefinierte Decoder
20% langsamer, da jeder Schlüssel in json auf die Notwendigkeit einer Transformation überprüft werden muss. Dies ist jedoch eine geringe Gebühr, da Schlüssel nicht explizit für Datenstrukturen durch Implementierung von Codable
registriert werden Codable
. Durch die Hinzufügung dieses Decoders wird die Anzahl der Kesselplatten im Projekt stark reduziert. Sollte ich damit Entwicklerzeit sparen, aber die Leistung verschlechtern? Es liegt an dir.
Vollständiger Beispielcode in der Github-Bibliothek
Englischer Artikel