Swift 5.0中的高级字符串插值



字符串插值是早期版本的Swift中的功能,但是在Swift 5.0中,此功能得到了扩展,变得更快,功能更强大。

在本文中,我们将探讨字符串插值的新可能性,并考虑如何将其应用于我们自己的代码中。 您也可以在这里下载本文的资源

基础知识


我们像这样使用基本的字符串插值:

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

我们认为这是理所当然的,但与以前必须处理的事情相比,这一次是一个重大的缓解:

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

此外,还可以显着提高性能,因为替代方案是:

 let all = s1 + s2 + s3 + s4 

是的,最终结果将是相同的,但是在分配全部之前,Swift必须在s2上添加s1以获得s5,在s3上添加s5以获得s6,在s4上添加s6以获得s7,然后再分配全部。

字符串插值在Swift 1.0中几乎没有变化,唯一的重大变化是在Swift 2.1中,我们有机会在插值中使用字符串文字

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

如您所知,Swift的发展很大程度上要归功于社区的建议。 讨论,发展想法,并接受或拒绝想法。

因此,五年后,Swift的开发便开始进行线插值。 Swift 5.0引入了新的超级功能,使我们能够控制字符串插值过程。

要尝试,请考虑以下情形。 如果我们像这样设置一个新的整数变量:

 let age = 38 

很明显,我们可以如下使用字符串插值:

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

但是,如果我们想用另一种方式格式化结果呢?

在Swift 5.0中使用新的字符串插值系统,我们可以编写扩展String.StringInterpolation来添加我们自己的插值方法:

 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) } } } 

现在,代码会将整个变量输出为文本:“嗨,我三十八岁。”

我们可以使用类似的技术来修复日期格式,因为默认的日期视图作为字符串不是很吸引人:

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

您将看到Swift以如下形式显示当前日期:“ 2019-02-21 23:30:21 +0000”。 我们可以使用自己的日期格式使它更漂亮:

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

现在结果看起来好多了,就像:“ 2019年2月21日23:30:21”。

注意:为避免在团队中一起工作时可能造成的混乱,您可能不应覆盖默认的Swift方法。 因此,请为参数指定您选择的名称,以避免混淆:

 mutating func appendInterpolation(format value: Int) { 

现在,我们将使用命名参数调用此方法:

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

现在很明显,我们正在使用我们自己的方法实现。

参数插补


此更改表明,我们现在可以完全控制字符串内插的发生方式。

例如,我们可以重写代码来处理Twitter消息:

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

现在我们可以这样写:

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

但是为什么我们要限制自己一个参数呢? 对于我们的数字格式示例,强制用户使用一个转换参数(.spellOut)没有意义-因此,我们将通过添加第二个参数来更改方法:

 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) } } 

并像这样使用它:

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

您可以根据需要选择任意类型的参数。 使用@autoclosure作为默认值的示例:

 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").") 

使用@autoclosure属性意味着对于默认值,我们可以使用简单值或调用复杂函数。 在该方法中,它们将成为闭包。

现在,您可能会认为我们可以在不使用插值功能的情况下重写代码,如下所示:

 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")).") 

但是现在我们使调用变得复杂了-因为我们显然正在尝试格式化某些内容,所以这就是插值的重点。 记住Swift规则-避免使用不必要的单词。

埃里卡·萨顿(Erica Sadun)提供了一个非常简短的示例来简化代码:

 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 ? "(*)" : "")") 

为自定义类型添加字符串插值


我们可以对自己的类型使用字符串插值:

 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)") 

字符串插值很有用,因为我们不涉及有关对象的调试信息。 如果我们在调试器中查看它或显示它,那么我们将看到未触及的数据:

 print(hater) 

我们可以将自定义类型与几个参数结合起来:

 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)") 

当然,您可以使用Swift的所有功能来创建自己的格式。 例如,我们可以编写一个接受任何Encodable并以JSON打印的实现:

 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) } } 


如果我们使Person符合Encodable协议,那么我们可以这样做:

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

您可以使用诸如可变数量的参数之类的功能,甚至可以将插值的实现标记为throwing 。 例如,我们的JSON格式化系统在发生编码错误时不响应,但是我们可以修复此错误以在将来分析错误:

 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)") 

到目前为止,我们所看到的只是对字符串插值方法的修改。

使用插值创建自定义类型


如您所见,这是一个有关如何以一种非常方便的方式格式化应用程序中的数据的问题,但是我们也可以使用字符串插值来创建自己的类型。

为了演示这一点,我们将创建一个新类型,该类型使用字符串插值从字符串初始化。

 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)" 

实际上,引擎盖下只有一种语法糖。 我们可以手动编写最后一部分:

 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) 

结论


如您所见,自定义字符串插值使我们可以将格式放在一个地方,从而使方法调用变得更加简单明了。 它还为我们提供了极大的灵活性,以尽可能自然地创建所需的类型。

请记住,这只是可能性之一,而不是唯一的可能性。 这意味着有时我们使用插值法,有时使用函数或其他方法。 像许多开发中一样,您始终需要选择解决问题的最佳方法。

Source: https://habr.com/ru/post/zh-CN447586/


All Articles