StringInterpolation

字符串表达协议

ExpressibleByStringLiteral协议

Swift
/// A type that can be initialized with a string literal.
/// The `String` and `StaticString` types conform to the
/// `ExpressibleByStringLiteral` protocol. You can initialize a variable or
/// constant of either of these types using a string literal of any length.
///     let picnicGuest = "Deserving porcupine"
/// Conforming to ExpressibleByStringLiteral
/// ========================================
/// To add `ExpressibleByStringLiteral` conformance to your custom type,
/// implement the required initializer.
public protocol ExpressibleByStringLiteral : ExpressibleByExtendedGraphemeClusterLiteral {
    /// A type that represents a string literal.
    /// Valid types for `StringLiteralType` are `String` and `StaticString`. associatedtype StringLiteralType : _ExpressibleByBuiltinStringLiteral
    /// Creates an instance initialized to the given string value.
    /// - Parameter value: The value of the new instance.
    init(stringLiteral value: Self.StringLiteralType)
}
// 默认实现的 init方法
extension ExpressibleByStringLiteral where Self.ExtendedGraphemeClusterLiteralType == Self.StringLiteralType {
    /// Creates an instance initialized to the given value.
    /// - Parameter value: The value of the new instance.
    public init(extendedGraphemeClusterLiteral value: Self.StringLiteralType)

}

ExpressibleByStringInterpolation协议

Swift
public protocol ExpressibleByStringInterpolation : ExpressibleByStringLiteral {
    /// The type each segment of a string literal containing interpolations
    /// should be appended to.
    /// The `StringLiteralType` of an interpolation type must match the
    /// `StringLiteralType` of the conforming type.
    associatedtype StringInterpolation : StringInterpolationProtocol = DefaultStringInterpolation where Self.StringLiteralType == Self.StringInterpolation.StringLiteralType
    /// Creates an instance from a string interpolation.
    /// Most `StringInterpolation` types will store information about the
    /// literals and interpolations appended to them in one or more properties.
    /// `init(stringInterpolation:)` should use these properties to initialize
    /// the instance.
    /// - Parameter stringInterpolation: An instance of `StringInterpolation`
    ///             which has had each segment of the string literal appended
    ///             to it.
    init(stringInterpolation: Self.StringInterpolation)
}

// 默认实现的 init方法(注意 Where 条件)
extension ExpressibleByStringInterpolation where Self.StringInterpolation == DefaultStringInterpolation {
    /// Creates a new instance from an interpolated string literal.
    /// Don't call this initializer directly. It's used by the compiler when
    /// you create a string using string interpolation. Instead, use string
    /// interpolation to create a new string by including values, literals,
    /// variables, or expressions enclosed in parentheses, prefixed by a
    /// backslash (`\(`...`)`).
    ///     let price = 2
    ///     let number = 3
    ///     let message = """
    ///                   If one cookie costs \(price) dollars, \
    ///                   \(number) cookies cost \(price * number) dollars.
    ///                   """
    ///     // message == "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
    public init(stringInterpolation: DefaultStringInterpolation)
}

字符串相关

StringProtocol协议

Swift
/// A type that can represent a string as a collection of characters.
/// Do not declare new conformances to `StringProtocol`. Only the `String` and
/// `Substring` types in the standard library are valid conforming types.
public protocol StringProtocol : BidirectionalCollection, Comparable, ExpressibleByStringInterpolation, Hashable, LosslessStringConvertible, TextOutputStream, TextOutputStreamable where Self.Element == Character, Self.Index == String.Index, Self.StringInterpolation == DefaultStringInterpolation, Self.SubSequence : StringProtocol

摘掉和本文不相关的协议和约束,剩下

Swift
/// A type that can represent a string as a collection of characters.
/// Do not declare new conformances to `StringProtocol`. Only the `String` and
/// `Substring` types in the standard library are valid conforming types.
public protocol StringProtocol : ExpressibleByStringInterpolation

String 结构体

Swift
// String 实现协议 ExpressibleByStringInterpolation init 方法
@frozen public struct String {
    @inlinable public init()
    /// Creates a new instance from an interpolated string literal.
    /// Do not call this initializer directly. It is used by the compiler when
    /// you create a string using string interpolation. Instead, use string
    /// interpolation to create a new string by including values, literals,
    /// variables, or expressions enclosed in parentheses, prefixed by a
    /// backslash (`\(`...`)`).
    ///     let price = 2
    ///     let number = 3
    ///     let message = """
    ///                   If one cookie costs \(price) dollars, \
    ///                   \(number) cookies cost \(price * number) dollars.
    ///                   """
    ///     print(message)
    ///     // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
    @inlinable public init(stringInterpolation: DefaultStringInterpolation)
    ......
}
Swift
// String 实现协议 ExpressibleByStringInterpolation 关联类型
extension String : StringProtocol {
    /// The type each segment of a string literal containing interpolations
    /// should be appended to.
    /// The `StringLiteralType` of an interpolation type must match the
    /// `StringLiteralType` of the conforming type.
    public typealias StringInterpolation = DefaultStringInterpolation

}

// String 实现协议 ExpressibleByStringLiteral
//(ExpressibleByStringInterpolation 的继承)
extension String : ExpressibleByStringLiteral {
    /// Creates an instance initialized to the given string value.
    /// Do not call this initializer directly. It is used by the compiler when you
    /// initialize a string using a string literal. For example:
    ///     let nextStop = "Clark & Lake"
    /// This assignment to the `nextStop` constant calls this string literal
    /// initializer behind the scenes.
    @inlinable public init(stringLiteral value: String)
    /// A type that represents an extended grapheme cluster literal.
    /// Valid types for `ExtendedGraphemeClusterLiteralType` are `Character`,
    /// `String`, and `StaticString`.
    public typealias ExtendedGraphemeClusterLiteralType = String
    /// A type that represents a string literal.
    /// Valid types for `StringLiteralType` are `String` and `StaticString`.
    public typealias StringLiteralType = String
    /// A type that represents a Unicode scalar literal.
    /// Valid types for `UnicodeScalarLiteralType` are `Unicode.Scalar`,
    /// `Character`, `String`, and `StaticString`.
    public typealias UnicodeScalarLiteralType = String
}

字符串插值协议与默认的插值器(暂且叫它)

StringInterpolationProtocol协议

Swift
public protocol StringInterpolationProtocol {
    /// The type that should be used for literal segments.
    associatedtype StringLiteralType : _ExpressibleByBuiltinStringLiteral
    init(literalCapacity: Int, interpolationCount: Int)
    mutating func appendLiteral(_ literal: Self.StringLiteralType)
}

DefaultStringInterpolation 结构体(插值器)

Swift
@frozen public struct DefaultStringInterpolation : StringInterpolationProtocol, Sendable {
    /// Creates a string interpolation with storage pre-sized for a literal
    /// with the indicated attributes.
    /// Do not call this initializer directly. It is used by the compiler when
 /// interpreting string interpolations.
    @inlinable public init(literalCapacity: Int, interpolationCount: Int)
    /// Appends a literal segment of a string interpolation.
    /// Do not call this method directly. It is used by the compiler when
    /// interpreting string interpolations.
    @inlinable public mutating func appendLiteral(_ literal: String)
    /// Interpolates the given value's textual representation into the
    /// string literal being created.
    /// Do not call this method directly. It is used by the compiler when
    /// interpreting string interpolations. Instead, use string
    /// interpolation to create a new string by including values, literals,
    /// variables, or expressions enclosed in parentheses, prefixed by a
    /// backslash (`\(`...`)`).
    ///     let price = 2
    ///     let number = 3
    ///     let message = """
    ///                   If one cookie costs \(price) dollars, \
    ///                   \(number) cookies cost \(price * number) dollars.
    ///                   """
    ///     print(message)
    ///     // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
    @inlinable public mutating func appendInterpolation<T>(_ value: T) where T : CustomStringConvertible, T : TextOutputStreamable
    /// Interpolates the given value's textual representation into the
    /// string literal being created.
    /// Do not call this method directly. It is used by the compiler when
    /// interpreting string interpolations. Instead, use string
    /// interpolation to create a new string by including values, literals,
    /// variables, or expressions enclosed in parentheses, prefixed by a
    /// backslash (`\(`...`)`).
    ///     let price = 2
    ///     let number = 3
    ///     let message = "If one cookie costs \(price) dollars, " +
    ///                   "\(number) cookies cost \(price * number) dollars."
    ///     print(message)
    ///     // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
    @inlinable public mutating func appendInterpolation<T>(_ value: T) where T : TextOutputStreamable
    /// Interpolates the given value's textual representation into the
    /// string literal being created.
    /// Do not call this method directly. It is used by the compiler when
    /// interpreting string interpolations. Instead, use string
    /// interpolation to create a new string by including values, literals,
    /// variables, or expressions enclosed in parentheses, prefixed by a
    /// backslash (`\(`...`)`).
    ///     let price = 2
    ///     let number = 3
    ///     let message = """
    ///                   If one cookie costs \(price) dollars, \
    ///                   \(number) cookies cost \(price * number) dollars.
    ///                   """
    ///     print(message)
    ///     // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."

    @inlinable public mutating func appendInterpolation<T>(_ value: T) where T : CustomStringConvertible
    /// Interpolates the given value's textual representation into the
    /// string literal being created.
    /// Do not call this method directly. It is used by the compiler when
    /// interpreting string interpolations. Instead, use string
    /// interpolation to create a new string by including values, literals,
    /// variables, or expressions enclosed in parentheses, prefixed by a
    /// backslash (`\(`...`)`).
    ///     let price = 2
    ///     let number = 3
    ///     let message = """
    ///                   If one cookie costs \(price) dollars, \
    ///                   \(number) cookies cost \(price * number) dollars.
    ///                   """
    ///     print(message)
    ///     // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
    @inlinable public mutating func appendInterpolation<T>(_ value: T)
    public mutating func appendInterpolation(_ value: Any.Type)
    /// The type that should be used for literal segments.
    public typealias StringLiteralType = String
}

实例

案例一:实现自定义字符串插值器,通过插值器创建实例

Swift
struct Person {
  let des: String
}
extension Person: ExpressibleByStringLiteral {
  init(stringLiteral value: String) {
    self.des = value
  }
}
extension Person: CustomStringConvertible {
  var description: String {
    return self.des
  }
}
// 字符串插值器(来自网络)
struct StringInterpolationC: 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) is cool]")
    }
    mutating func appendInterpolation(issue number: Int) {
        self.parts.append("[forever \(number) years old]")
    }
}
extension Person: ExpressibleByStringInterpolation {
    typealias StringInterpolation = StringInterpolationC
    init(stringInterpolation: StringInterpolationC) {
        let string = stringInterpolation.parts.joined()
        self.init(stringLiteral: string)
    }
}
func expressTest() {
    let person: Person = "Hello \(user: "Boat") and \(user: "xin")"
    let person2: Person = "Hello \(user: "Boat") and \(issue: 18)"
    print(person)
    print(person2)
//    let pperson =  PPerson(name: "xin weizhou", nickName: "zhou ge")
//    let str1: String = "Hello \(p: pperson)"
//    let str2: String = "Hello \(p: pperson, isFriend: true)"
//    print(str1)
//    print(str2)
}

案例二:扩展插值器功能,用以支持自定义差值类型,本次案例扩展的是默认插值器DefaultStringInterpolation

Swift
struct PPerson {
    let name: String
    // 巴铁的话一般叫昵称就行了
    var nickName: String
    // 根据朋友关系,返回称呼
    func getTitle(isFriend: Bool) -> String {
        isFriend ? nickName : name
    }
}
// 直接扩展DefaultStringInterpolation
extension DefaultStringInterpolation: StringInterpolationProtocol {
    mutating func appendInterpolation(p person: PPerson) {
        // 调用的 `appendLiteral(_ literal: String)` 接受 `String` 参数
        appendLiteral("\(person.name)")
    }
}
// 间接扩展DefaultStringInterpolation
extension String.StringInterpolation {
    mutating func appendInterpolation(p person: PPerson, isFriend: Bool) {
        appendLiteral(person.getTitle(isFriend: isFriend))
    }
}

遗留疑问?

通过上面的例子可知,Person 会通过“插值器”拿到保存的数据;那么问题来了,案例二中String也会通过“默认插值器”(DefaultStringInterpolation)取得数据,但是我并没有发现“默认插值器”有给外界提供数据的接口。另外我尝试了在DefaultStringInterpolation扩展中添加description方法,对上面案例二的测试代码结果并没有影响。

Swift
extension DefaultStringInterpolation : CustomStringConvertible {
    /// A textual representation of this instance.
    ///
    /// Calling this property directly is discouraged. Instead, convert an
    /// instance of any type to a string by using the `String(describing:)`
    /// initializer. This initializer works with any type, and uses the custom
    /// `description` property for types that conform to
    /// `CustomStringConvertible`:
    ///     struct Point: CustomStringConvertible {
    ///         let x: Int, y: Int
    ///         var description: String {
    ///             return "(\(x), \(y))"
    ///         }
    ///     }
    ///     let p = Point(x: 21, y: 30)
    ///     let s = String(describing: p)
    ///     print(s)
    ///     // Prints "(21, 30)"
    ///
    /// The conversion of `p` to a string in the assignment to `s` uses the
    /// `Point` type's `description` property.
    @inlinable public var description: String { get }
}
extension DefaultStringInterpolation  {
     var description: String { return "Hello" }
}

附件

https://blog.csdn.net/weixin_34220963/article/details/91452976
https://developer.apple.com/documentation/swift/expressiblebystringinterpolation
https://onevcat.com/2021/03/swiftui-text-1/
https://www.jianshu.com/p/7c2424be8b56

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,333评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,812评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,016评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,293评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,650评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,788评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,003评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,741评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,462评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,681评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,168评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,528评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,169评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,119评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,846评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,719评论 2 274

推荐阅读更多精彩内容