Erweiterte String-Interpolation in Swift 5.0



Die String-Interpolation war in Swift aus früheren Versionen, aber in Swift 5.0 wurde diese Funktionalität erweitert, schneller und deutlich leistungsfähiger.

In diesem Artikel gehen wir auf die neuen Möglichkeiten der String-Interpolation ein und überlegen, wie dies in unserem eigenen Code angewendet werden kann. Sie können die Quellen für diesen Artikel auch hier herunterladen .

Die Grundlagen


Wir verwenden die grundlegende String-Interpolation wie folgt:

let age = 38 print("You are \(age)") 

Wir halten dies für selbstverständlich, aber zu einer Zeit war es eine bedeutende Erleichterung im Vergleich zu dem, was wir zuvor zu tun hatten:

 [NSString stringWithFormat:@"%ld", (long)unreadCount]; 

Es gibt auch einen signifikanten Leistungsgewinn, da die Alternative war:

 let all = s1 + s2 + s3 + s4 

Ja, das Endergebnis wäre das gleiche, aber Swift müsste s1 zu s2 hinzufügen, um s5 zu erhalten, s5 zu s3 hinzufügen, um s6 zu erhalten, und s6 zu s4 hinzufügen, um s7 zu erhalten, bevor alle zugewiesen werden.

Die String-Interpolation hat sich mit Swift 1.0 praktisch nicht geändert. Die einzige signifikante Änderung kam mit Swift 2.1, wo wir die Möglichkeit hatten, String-Literale für die Interpolation zu verwenden:

 print("Hi, \(user ?? "Anonymous")") 

Wie Sie wissen, entwickelt sich Swift hauptsächlich dank der Vorschläge der Community. Ideen werden diskutiert, entwickelt und entweder akzeptiert oder abgelehnt.

Fünf Jahre später begann Swifts Entwicklung mit der Linieninterpolation. Swift 5.0 führt neue Superfunktionen ein, mit denen wir den String-Interpolationsprozess steuern können.

Stellen Sie sich zum Versuch das folgende Szenario vor. Wenn wir eine neue Ganzzahlvariable wie folgt setzen:

 let age = 38 

Es ist ziemlich offensichtlich, dass wir die String-Interpolation wie folgt verwenden können:

 print("Hi, I'm \(age).") 

Was aber, wenn wir das Ergebnis anders formatieren wollen?

Mit dem neuen String-Interpolationssystem in Swift 5.0 können wir die Erweiterung String.StringInterpolation schreiben, um unsere eigene Interpolationsmethode hinzuzufügen:

 extension String.StringInterpolation { mutating func appendInterpolation(_ value: Int) { let formatter = NumberFormatter() formatter.numberStyle = .spellOut if let result = formatter.string(from: value as NSNumber) { appendLiteral(result) } } } 

Jetzt gibt der Code die gesamte Variable als Text aus: "Hallo, ich bin achtunddreißig."

Wir können eine ähnliche Technik verwenden, um die Datumsformatierung zu korrigieren, da die Standard-Datumsansicht als Zeichenfolge nicht sehr attraktiv ist:

 print("Today's date is \(Date()).") 

Sie werden sehen, dass Swift das aktuelle Datum in Form von "2019-02-21 23:30:21 +0000" anzeigt. Wir können es mit unserer eigenen Datumsformatierung schöner machen:

 mutating func appendInterpolation(_ value: Date) { let formatter = DateFormatter() formatter.dateStyle = .full let dateString = formatter.string(from: value) appendLiteral(dateString) } 

Jetzt sieht das Ergebnis viel besser aus, etwa: „21. Februar 2019, 23:30:21“.

Hinweis: Um mögliche Verwirrung bei der Zusammenarbeit in einem Team zu vermeiden, sollten Sie die Standard-Swift-Methoden wahrscheinlich nicht überschreiben. Geben Sie den Parametern daher die Namen Ihrer Wahl, um Verwirrung zu vermeiden:

 mutating func appendInterpolation(format value: Int) { 

Jetzt werden wir diese Methode mit einem benannten Parameter aufrufen:

 print("Hi, I'm \(format: age).") 

Jetzt wird klar sein, dass wir unsere eigene Implementierung der Methode verwenden.

Interpolation mit Parametern


Diese Änderung zeigt, dass wir jetzt die volle Kontrolle darüber haben, wie die String-Interpolation erfolgt.

Zum Beispiel können wir den Code neu schreiben, um Twitter-Nachrichten zu verarbeiten:

 mutating func appendInterpolation(twitter: String) { appendLiteral("<a href=\"https://twitter.com/\(twitter)\">@\(twitter)</a>") } 

Jetzt können wir so schreiben:

 print("You should follow me on Twitter: \(twitter: "twostraws").") 

Aber warum sollten wir uns auf einen Parameter beschränken? In unserem Beispiel für die Formatierung von Zahlen ist es nicht sinnvoll, Benutzer zur Verwendung eines Konvertierungsparameters (.spellOut) zu zwingen. Daher ändern wir die Methode durch Hinzufügen eines zweiten Parameters:

 mutating func appendInterpolation(format value: Int, using style: NumberFormatter.Style) { let formatter = NumberFormatter() formatter.numberStyle = style if let result = formatter.string(from: value as NSNumber) { appendLiteral(result) } } 

Und benutze es so:

 print("Hi, I'm \(format: age, using: .spellOut).") 

Sie können beliebig viele Parameter eines beliebigen Typs verwenden. Beispiel mit @autoclosure als Standardwert:

 extension String.StringInterpolation { mutating func appendInterpolation(_ values: [String], empty defaultValue: @autoclosure () -> String) { if values.count == 0 { appendLiteral(defaultValue()) } else { appendLiteral(values.joined(separator: ", ")) } } } let names = ["Malcolm", "Jayne", "Kaylee"] print("Crew: \(names, empty: "No one").") 

Die Verwendung des Attributs @autoclosure bedeutet, dass wir für den Standardwert einfache Werte verwenden oder komplexe Funktionen aufrufen können. Bei der Methode werden sie zu einem Verschluss.

Jetzt denken Sie vielleicht, dass wir den Code ohne Verwendung der Interpolationsfunktion neu schreiben können.

 extension Array where Element == String { func formatted(empty defaultValue: @autoclosure () -> String) -> String { if count == 0 { return defaultValue() } else { return self.joined(separator: ", ") } } } print("Crew: \(names.formatted(empty: "No one")).") 

Aber jetzt haben wir den Aufruf kompliziert - weil wir offensichtlich versuchen, etwas zu formatieren, ist dies der Punkt der Interpolation. Denken Sie an die Swift-Regel - vermeiden Sie unnötige Wörter.

Erica Sadun bot ein wirklich kurzes und schönes Beispiel für die Vereinfachung des Codes:

 extension String.StringInterpolation { mutating func appendInterpolation(if condition: @autoclosure () -> Bool, _ literal: StringLiteralType) { guard condition() else { return } appendLiteral(literal) } } let doesSwiftRock = true print("Swift rocks: \(if: doesSwiftRock, "(*)")") print("Swift rocks \(doesSwiftRock ? "(*)" : "")") 

Hinzufügen einer Zeichenfolgeninterpolation für benutzerdefinierte Typen


Wir können die String-Interpolation für unsere eigenen Typen verwenden:

 struct Person { var type: String var action: String } extension String.StringInterpolation { mutating func appendInterpolation(_ person: Person) { appendLiteral("I'm a \(person.type) and I'm gonna \(person.action).") } } let hater = Person(type: "hater", action: "hate") print("Status check: \(hater)") 

Die String-Interpolation ist nützlich, da wir das Debuggen von Informationen zu einem Objekt nicht berühren. Wenn wir es uns im Debugger ansehen oder anzeigen, sehen wir unberührte Daten:

 print(hater) 

Wir können einen benutzerdefinierten Typ mit mehreren Parametern kombinieren:

 extension String.StringInterpolation { mutating func appendInterpolation(_ person: Person, count: Int) { let action = String(repeating: "\(person.action) ", count: count) appendLiteral("\n\(person.type.capitalized)s gonna \(action)") } } let player = Person(type: "player", action: "play") let heartBreaker = Person(type: "heart-breaker", action: "break") let faker = Person(type: "faker", action: "fake") print("Let's sing: \(player, count: 5) \(hater, count: 5) \(heartBreaker, count: 5) \(faker, count: 5)") 

Natürlich können Sie alle Funktionen von Swift verwenden, um Ihre eigene Formatierung zu erstellen. Zum Beispiel können wir eine Implementierung schreiben, die alle codierbaren Elemente akzeptiert und in JSON druckt:

 mutating func appendInterpolation<T: Encodable>(debug value: T) { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted if let result = try? encoder.encode(value) { let str = String(decoding: result, as: UTF8.self) appendLiteral(str) } } 


Wenn wir Person mit dem Encodable- Protokoll kompatibel machen, können wir dies tun:

 print("Here's some data: \(debug: faker)") 

Sie können Funktionen wie eine variable Anzahl von Parametern verwenden und sogar Ihre Implementierung der Interpolation als Auslösen markieren. Zum Beispiel reagiert unser JSON-Formatierungssystem nicht im Falle eines Codierungsfehlers, aber wir können dies beheben, um den Fehler in Zukunft zu analysieren:

 mutating func appendInterpolation<T: Encodable>(debug value: T) throws { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let result = try encoder.encode(value) let str = String(decoding: result, as: UTF8.self) appendLiteral(str) } print(try "Status check: \(debug: hater)") 

Alles, was wir bisher betrachtet haben, sind nur Änderungen an den String-Interpolationsmethoden.

Erstellen benutzerdefinierter Typen mithilfe der Interpolation


Wie Sie gesehen haben, ging es darum, wie Sie die Daten in Ihrer Anwendung auf sehr bequeme Weise formatieren können. Wir können jedoch auch eigene Typen mithilfe der Zeichenfolgeninterpolation erstellen.

Um dies zu demonstrieren, erstellen wir einen neuen Typ, der mithilfe einer Zeichenfolgeninterpolation aus einem String initialisiert wird.

 struct ColoredString: ExpressibleByStringInterpolation { //    -    -    struct StringInterpolation: StringInterpolationProtocol { //   -    var output = NSMutableAttributedString() //     var baseAttributes: [NSAttributedString.Key: Any] = [.font: UIFont(name: "Georgia-Italic", size: 64) ?? .systemFont(ofSize: 64), .foregroundColor: UIColor.black] //   ,        init(literalCapacity: Int, interpolationCount: Int) { } // ,      mutating func appendLiteral(_ literal: String) { //   ,       print("Appending \(literal)") //    let attributedString = NSAttributedString(string: literal, attributes: baseAttributes) //     output.append(attributedString) } // ,          mutating func appendInterpolation(message: String, color: UIColor) { //     print("Appending \(message)") //        var coloredAttributes = baseAttributes coloredAttributes[.foregroundColor] = color //    -     let attributedString = NSAttributedString(string: message, attributes: coloredAttributes) output.append(attributedString) } } //    ,     let value: NSAttributedString //      init(stringLiteral value: String) { self.value = NSAttributedString(string: value) } //     init(stringInterpolation: StringInterpolation) { self.value = stringInterpolation.output } } let str: ColoredString = "\(message: "Red", color: .red), \(message: "White", color: .white), \(message: "Blue", color: .blue)" 

In der Tat gibt es einen syntaktischen Zucker unter der Haube. Wir könnten den letzten Teil manuell schreiben:

 var interpolation = ColoredString.StringInterpolation(literalCapacity: 10, interpolationCount: 1) interpolation.appendLiteral("Hello") interpolation.appendInterpolation(message: "Hello", color: .red) interpolation.appendLiteral("Hello") let valentine = ColoredString(stringInterpolation: interpolation) 

Fazit


Wie Sie gesehen haben, können wir durch benutzerdefinierte Zeichenfolgeninterpolation die Formatierung an einer Stelle platzieren, sodass Methodenaufrufe einfacher und klarer werden. Es bietet uns auch große Flexibilität, um die erforderlichen Typen so natürlich wie möglich zu erstellen.

Denken Sie daran, dass dies nur eine der Möglichkeiten ist - und nicht die einzige. Dies bedeutet, dass wir manchmal Interpolation verwenden, manchmal Funktionen oder etwas anderes. Wie viel in der Entwicklung müssen Sie immer den besten Weg wählen, um das Problem zu lösen.

Source: https://habr.com/ru/post/de447586/


All Articles