58

Swift 5 字符串插值-简介

 5 years ago
source link: https://swift.gg/2019/04/22/swift5-stringinterpolation-part1/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

作者:Olivier Halligon, 原文链接 ,原文日期:2018-12-15

译者: Nemocdz ;校对: numbbbbbYousanflics ;定稿: Forelax

StringInterpolation 协议最初的设计效率低下又不易扩展,为了在后续的版本中能够将其彻底重构,Swift 4 中将该协议标记为废弃。即将在 Swift 5 中亮相的 SE-0228 提案介绍了一种新的 StringInterpolation 设计,使得 String 有了更大的潜能。

在 Swift 的 master 分支里实现之后,就可以下载一个 快照 来安装最新的 Swift 5 工具链到 Xcode 中,来尝试全新的 StringInterpolation 。让我们来把玩一下。

全新的 StringInterpolation 设计

我强烈建议本篇文章的读者阅读一下 SE-0228 提案,感受一下新 API 的背后的设计思路和动机。

要让一个类型遵循 ExpressibleByStringInterpolation ,最基本的你需要:

  • 让这个类型拥有一个类型为 StringInterpolation 的子类型,这个子类型遵循 StringInterpolationProtocol 并将负责解释插值
  • 这个子类型仅需要实现 appendLiteral(_ literal: String) 方法,再选择一个或多个你自己想要支持的 appendInterpolation(...) 签名的方法
  • 这个 StringInterpolation 子类型会作为“构造器”服务于你的主类型,然后编译器会调用那些 append… 方法一步一步地构造对象
  • 然后你的主类型需要实现 init(stringInterpolation: StringInterpolation) ,它会用上一步的结果来实例化它自己。

你可以实现任何你喜欢的 appenInterpolation(...) 方法,这意味着你可以任意选择支持什么插值。这是一个带来巨大的可能性的超强功能。

举个例子,如果你实现了 func appendInterpolation(_ string: String, pad: Int) ,那么意味着你将可以用类似这样的插值: "Hello \(name, pad: 10), how are you?" 来构造你的类型。插值只需要匹配你的 StringInterpolation 子类型其中一个支持的 appendInterpolation 方法签名。

一个简单的例子

让我用一个简单的例子来演示一下插值是如何运作的。一起来构造一个允许引用 issue 编号和用户的 GitHubComment 类型吧。

这个例子的目标是做到类似下面的写法:

let comment: GitHubComment = """
  See \(issue: 123) where \(user: "alisoftware") explains the steps to reproduce.
  """

所以我们该怎么实现它呢?

首先,让我们声明基本的结构体 struct GitHubComment 并让它遵循 ExpressibleByStringLiteral (因为 ExpressibleByStringInterpolation 继承自这个协议所以我们将它的实现抽离)和 CustomStringConvertible (为了 debug 时友好地在控制台中打印)。

struct GitHubComment {
  let markdown: String
}
extension GitHubComment: ExpressibleByStringLiteral {
  init(stringLiteral value: String) {
    self.markdown = value
  }
}
extension GitHubComment: CustomStringConvertible {
  var description: String {
    return self.markdown
  }
}

然后,我们让 GitHubComment 遵循 ExpressibleByStringInterpolation 。这意味着在剩下需要实现的功能,将由一个 StringInterpolation 子类型来完成:

  • 首先初始化它自己: init(literalCapacity: Int, interpolationCount: Int) 提供给你保留一部分数据到缓冲区的能力,在一步步构造类型时就会用到这个能力。在这个例子中,我们可以在构造实例的时候,简单用一个 String 并往它上面追加片段,不过这里我采用一个 parts: [String] 来代替,之后再将它组合起来

  • 实现 appendLiteral(_ string: String) 逐个追加文本到 parts

  • 实现 appendInterpolation(user: String) 在遇到 \(user: xxx) 时逐个追加 markdown 表示的用户配置链接

  • 实现 appendInterpolation(issue: Int) 逐个追加用 markdown 表示的 issue 链接

  • 然后在 GitHubComment 上实现 init(stringInterpolation: StringInterpolation)parts 构造成一个评论

extension GitHubComment: ExpressibleByStringInterpolation {
  struct StringInterpolation: StringInterpolationProtocol {
    var parts: [String]
      init(literalCapacity: Int, interpolationCount: Int) {
      self.parts = []
      // - literalCapacity 文本片段的字符数 (L)
      // - interpolationCount 插值片段数 (I)
      // 我们预计通常结构会是像 "LILILIL"
      // — e.g. "Hello \(world, .color(.blue))!" — 因此是 2n+1
      self.parts.reserveCapacity(2*interpolationCount+1)
    }
      mutating func appendLiteral(_ literal: String) {
      self.parts.append(literal)
    }
    mutating func appendInterpolation(user name: String) {
      self.parts.append("[\(name)](https://github.com/\(name))")
    }
    mutating func appendInterpolation(issue number: Int) {
      self.parts.append("[#\(number)](issues/\(number))")
    }
  }
  init(stringInterpolation: StringInterpolation) {
    self.markdown = stringInterpolation.parts.joined()
  }
}

这就完事了!我们成功了!

注意,因为那些我们实现了的 appendInterpolation 方法签名,我们允许使用 Hello \(user: "alisoftware") 但不能使用 Hello \(user: 123) ,因为 appendInterpolation(user:) 期望一个 String 作为形参。类似的是,在你的字符串中 \(issue: 123) 只能允许一个 Int 因为 appendInterpolation(issue:) 采用一个 Int 作为形参。

实际上,如果你尝试在你的 StringInterpolation 子类中用不支持的插值,编译器会给你提示报错:

let comment: GitHubComment = """
See \(issue: "bob") where \(username: "alisoftware") explains the steps to reproduce.
"""
//             ^~~~~         ^~~~~~~~~
// 错误: 无法转换 ‘String’ 类型的值到期望的形参类型 ‘Int’
// 错误: 调用 (have 'username:', expected 'user:')实参标签不正确

这仅仅只是个开始

这个新的设计打开了一大串脑洞让你去实现自己的 ExpressibleByStringInterpolation 类型。这些想法包括:

  • 创建一个 HTML 类型并遵循,你就可以用插值写 HTML
  • 创建一个 SQLStatement 类型并遵循,你就可以写更简单的 SQL 语句
  • 用字符串插值支持更多自定义格式,比如在你的插值字符串中用格式化 Double 或者 Date
  • 创建一个 RegEX 类型并遵循,你就可以用花里胡哨的语法写正则表达式
  • 创建一个 AttributedString 类型并遵循,就可以用字符串插值构建 NSAttributedString

带来新的字符串插值设计的 Brent Royal-GordonMichael Ilseman ,提供了更多例子在这个 要点列表 中。

我个人尝试了一下支持 NSAttributedString 的实现,并想 在专门的一篇文章里分享它的初步实现 ,因为我发现它非常优雅。我们下一篇文章再见!

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问http://swift.gg。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK