Schnell. Serialisierung von Anforderungsparametern / Sudo Null IT News

xcodexcode

Sicherlich hat jeder Entwickler, der die Netzwerkschicht der Anwendung programmieren musste, das Problem der Übergabe von Anforderungsparametern gelöst. In den meisten Fällen ist dies eine einfache Aufgabe, die mit Standardtools gelöst werden kann, die vom nativen SDK oder der Programmiersprache bereitgestellt werden. Aber wenn wir die Situation im Kontext der iOS-Plattform und der Programmiersprache Swift betrachten, wird sofort klar, dass der Compiler einen Fehler wirft, wenn er versucht, Parameter als Wörterbuch zu serialisieren [String: Any]. Dank der Neuerungen in iOS 15.4 und Swift 5.6 ist dieses Wörterbuch jedoch viel einfacher zu serialisieren.

Eine Aufgabe

Lassen Sie RequestParameters = [
“method”: “createUser”,
“credentials”: [
“login”: login,
“password”: password,
“age”: age,
“notificationSettings”: [
“notifyNews”: isNotifyNews,
“notifyCabinet”: isCabinetNotify
]
]]

Mit diesem Ansatz wird es viel einfacher, das Projekt zu warten, da es die Erstellung von “unnötigen” Modellen eliminiert.

struct LoginRequest: Codable { struct Credentials: Codable { struct NotificationSettings: Codable { var notificationNews: Bool var notificationCabinet: Bool } var login: String var password: String var age: Int var notificationSettings: NotificationSettings } var method: String var Credentials: Credentials }

Wir werden sofort reservieren, dass es sich nicht lohnt, Parameter wie Login und Passwort in einer Netzwerkanfrage zu übergeben, da für diese Zwecke bereits veraltete Basic-Authentifizierungstechnologie sowie ein modernerer Ansatz mit Zugriffstoken und Aktualisierungstoken vorhanden sind.

Lassen Sie RequestParameters = [
“email”: email,
“firstName”: firstName,
“age”: age
]

war das gleiche in der Zeile selbst

email=beispiel@beispiel.com&vorname=Nickey&alter=21

Diese Anforderung ist für Fälle von Parameterverschlüsselung erforderlich (wenn zusätzlich ein verschlüsselter Hash einer bestimmten Zeichenfolge übertragen wird).

Lösung

Beginnen wir mit der Serialisierung der Parameter im Hauptteil der Anfrage. Dank des Protokolls CodingKeyRepresentabledie in iOS 15.4 und Technologie erschien Typ löscheneingeführt in Swift 5.6, hat es einfacher gemacht, den Any-Typ zu codieren.

Angenommen, es gibt einen Container für Parameter – Query, der ein Array als Speicher verwendet, um die Reihenfolge der Elemente beizubehalten, aber gleichzeitig alle Funktionen eines Wörterbuchs implementiert.

import Foundation public extension Request { struct Query { // MARK: – Types public struct Parameter { public var key: K public var value: V } // MARK: – Properties private var elements: [Element]

// MARK: – Lifecycle init(uniqueKeysWithValues ​​​​elements: S) where S.Element == (Key, Value) { self.elements = elements.map(Parameter.init) } // MARK: – Methoden öffentlicher Index (Schlüssel: Schlüssel) -> Wert? where Key: Equatable { get { elements.first { $0.key == key }?.value } set { if let index = elements.firstIndex(where: { $0.key == key }) { if let newValue { elements[index].value = newValue } else { elements.remove(at: index) } } else { if let newValue { elements.append(Element(key: key, value: newValue)) } } } } } } // MARK: – Erweiterungen Erweiterung Request.Query: ExpressibleByDictionaryLiteral { public typealias Value = any Encodable public init(dictionaryLiteral elements: (Key, Value)…) { self.elements = elements.map(Parameter.init) } } extension Request.Query: RangeReplaceableCollection { public init() { self.elements = []
} } Erweiterung Request.Query: Sequence { public typealias Iterator = IndexingIterator> public func makeIterator() -> Iterator { return elements.makeIterator() } } extension Request.Query: Collection { public typealias Element = Parameter< Schlüssel, Wert> public typealias Index = Array.Index public var startIndex: Index { return elements.startIndex } public var endIndex: Index { return elements.endIndex } public subscript(position: Index) -> Element { return elements[position]
} public func index(after i: Index) -> Index { return elements.index(after: i) } }

Um das CodingKeyRepresentable-Protokoll zu verwenden, benötigen Sie dann ein Objekt, das das CodingKey-Protokoll implementiert.

import Foundation struct QueryCodingKey: CodingKey { let stringValue: String let intValue: Int? init(stringValue: String) { self.stringValue = stringValue self.intValue = Int(stringValue) } init(intValue: Int) { self.stringValue = “\(intValue)” self.intValue = intValue } init(key: CodingKeyRepresentable) { self.stringValue = key.codingKey.stringValue self.intValue = key.codingKey.intValue } }

Um das Encodable-Protokoll zu implementieren, reicht es daher aus, die Methode encode(to:) zu programmieren.

Erweiterung Request.Query: Encodable { public func encode(to encoder: Encoder) throws { if Key.self is CodingKeyRepresentable.Type { var container = encoder.container(keyedBy: QueryCodingKey.self) for element in elements { guard let key = element .Schlüssel als? CodingKeyRepresentable else { Continue } let encodingKey = QueryCodingKey(key: key) try container.encode(element.value, forKey: encodingKey) } } else { var container = encoder.unkeyedContainer() for element in elements { try container.encode(element .key) versuche container.encode(element.value) } } } }

Als Nächstes lösen wir das Problem der Codierung in eine Abfragezeichenfolge. Erstens gibt es mehrere Möglichkeiten, ein Array und Bool-Werte zu codieren, also müssen Sie diese Optionen beispielsweise in Form von entsprechenden Enums beschreiben.

public struct QueryEncoding { public enum ArrayEncoding { case enclosingBrackets case SurroundingBrackets case noBrackets } public enum BoolEncoding { case numeric case literal } public var array: ArrayEncoding public var bool: BoolEncoding public init(array: QueryEncoding.ArrayEncoding = .enclosingBrackets, bool: QueryEncoding. BoolEncoding = .literal) { self.array = Array self.bool = bool } }

Außerdem sollten Sie zum Erstellen einer Abfragezeichenfolge die Standardwerkzeuge verwenden URL-Komponenten und URLQueryItem. Um also jeden Parameter in ein URLQueryItem umzuwandeln, reicht es aus, die entsprechende Methode zu deklarieren.

extension Request.Query where Key == String { public func encode(to url: URL, encoding: QueryEncoding) -> URL? { var components = URLComponents(url: url, resolvingAgainstBaseURL: false) components?.queryItems = elements.flatMap { encodeQueryItem(element: $0, encoding: encoding) } return components?.url } private func encodeQueryItem(element: Element, encoding: QueryEncoding) -> [URLQueryItem] { encodeQueryItem (Name: element.key, Wert: element.value, Codierung: Codierung) } Private Funktion encodeQueryItem (Name: String, Wert: Any, Codierung: QueryEncoding) -> [URLQueryItem] { switch value { case let boolean as Bool: let queryItem = encodeBool(name: name, value: boolean, encoding: encoding) return [queryItem]
case let number as NSNumber: let queryItem = encodeNumber(name: name, value: number) return [queryItem]
case let array as [Any]: let queryItems = encodeArray(name: name, value: array, encoding: encoding) return queryItems case let dictionary as [String: Any]: let queryItems = encodeDictionary(name: name, value: dictionary, encoding: encoding) return queryItems default: let queryItem = URLQueryItem(name: name, value: “\(value)”) return [queryItem]
} } private func encodeBool(name: String, value: Bool, encoding: QueryEncoding) -> URLQueryItem { let stringValue: String switch encoding.bool { case .numeric: stringValue = (value as NSNumber).stringValue case .literal: stringValue = String (Wert) } URLQueryItem zurückgeben (Name: Name, Wert: StringValue) } Private Funktion encodeNumber (Name: String, Wert: NSNumber) -> URLQueryItem { let stringValue = value.stringValue URLQueryItem zurückgeben (Name: Name, Wert: StringValue) } private func encodeArray(name: String, value: [Any]Kodierung: QueryEncoding) -> [URLQueryItem] { switch encoding.array { case .enclosingBrackets: Rückgabewert.flatMap { encodeQueryItem(name: name + “[]”, Wert: $0, Codierung: Codierung) } case .surroundingBrackets: let value = value .flatMap { encodeQueryItem(name: name, value: $0, encoding: encoding) } .compactMap { $0.value } .map { “\” \($0)\”” } .joined(separator: “,”) let queryItem = URLQueryItem(name: name, value: “[\(value)]”) Rückkehr [queryItem]
case .noBrackets: return value.flatMap { encodeQueryItem(name: name, value: $0, encoding: encoding) } } } private func encodeDictionary(name: String, value: [String: Any]Kodierung: QueryEncoding) -> [URLQueryItem] { Rückgabewert .map { encodeQueryItem(name: name + “[\($0)]”, Wert: $1, Kodierung: Kodierung) } .flatMap { $0 } } }

Ein aufmerksamer Leser mag fragen, warum die Einschränkung hinzugefügt wird?

wobei Schlüssel == Zeichenfolge

Diese Einschränkung ist auf den Typ (String) des ersten Felds der URLQueryItem-Struktur zurückzuführen.

Verwendungszweck

Im ersten Fall, wenn es erforderlich ist, Parameter im Hauptteil der Anfrage zu übergeben, ist die Methode zum Erstellen und Ausführen der Anfrage wie folgt

func createAccount(login: String, password: String, age: Int, isNotifyNews: Bool, isNotifyCabinet: Bool) async throws { let url = try createUrl(host: .staging, path: “api/v1/account/create”) let Überschriften: [HTTPHeader] = [
.contentType(“application/json”),
]

Parameter lassen: Request.Query = [
“method”: “createUser”,
“credentials”: [
“login”: login,
“password”: password,
“age”: age,
“notificationSettings”: [
“notifyNews”: isNotifyNews,
“notifyCabinet”: isNotifyCabinet
]
]]try await dataRequest( url: url, method: .post, headers: headers, parameters: .body(parameters) ) }

Im zweiten Fall, um eine Abfragezeichenfolge zu erstellen, wird die Abfrage unten dargestellt

func updateAccount(id: Int, email: String, firstName: String, age: Int) async throws { let url = try createUrl(host: .staging, path: “api/v1/account/\(id)”) let accessToken = try accessToken(for: .staging) let headers: [HTTPHeader] = [
.authorization(“Bearer \(accessToken)”)
]

Parameter lassen: Request.Query = [
“email”: email,
“firstName”: firstName,
“age”: age
]

try await dataRequest( url: url, method: .put, headers: headers, parameters: .query(parameters) ) }

Fazit

Früher, vor dem Aufkommen von CodingKeyRepresentable und Typlöschung, konnte diese Lösung auch programmiert werden, nur dafür musste zusätzlich ein AnyEncodable-Container erstellt und das Schlüsselfeld auf Übereinstimmung mit dem Typ String oder Int geprüft werden. Mit der Entwicklung der Plattform ist es jedoch viel bequemer geworden, mit dem Parameterwörterbuch zu arbeiten, und Sie können das nächste Anforderungsmodell endgültig vergessen.

  1. CodingKeyRepresentable

  2. Löschen eingeben

  3. URL-Komponenten

  4. URLQueryItem

Similar Posts

Leave a Reply

Your email address will not be published.