Swift 5.0中超强大的字符串插值

基础

我们已经习惯了像这样的字符串插值

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

与以前相比,语法已经极大的改进,
以前是这样的

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

性能也改进了,
以前可能是这样的

let all = s1 + s2 + s3 + s4 // Swift 需要s1+s2得到s5,s5+s3得到s6,s6+s4得到s7,最后分配到all。

字符串插值变化自Swift2.1,可以这样使用

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

现在Swift Evolution 不断推送Swift向前发展,Swift4.2中就引入了很多功能。
Swift5.0中,ABI稳定性是重点,字符串插值也获得了超级功能,我们可以更好的控制它。
Swift5.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)
        }
    }
}
let age = 18
print("I'm \(age)")

这时将打印 I'm eighteen。

为了避免混淆你的小伙伴,你不应该覆盖Swift的默认值,因此需要命名参数来避免混淆。

// 通过添加命名参数来区分默认的方法
mutating func appendInterpolation(format value: Int) {

参数插补

在方法上添加第二个type参数

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

参数也可以是其它类型。
Erica Sadun 给出的示例。

extension String.StringInterpolation {
    // autoclosure 自动闭包,适用于()->T这样的无参闭包
    mutating func appendInterpolation(if condition: @autoclosure () -> Bool, _ literal: StringLiteralType) {
        guard condition() else { return }
        appendLiteral(literal)
    }
}

使用它

let doesSwiftRock = true
print("Swift rocks: \(if: 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)

print还是原始数据

下面是一个接收泛型对象参数的自定义插值函数

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

带插值的构建类型

如上所述,插值是一种非常干净的控制数据格式化的方式。
我们也可以通过插值来构建自己的类型。

我们将使用字符串插值方法来创建带颜色的字符串的类型。

struct ColorString: 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) {
            let attributedString = NSAttributedString(string: literal, attributes: baseAttributes)
            output.append(attributedString)
        }
        
        // 添加带颜色的文字时
        mutating func appendInterpolation(message: String, color: UIColor) {
            var colorAtt = baseAttributes
            colorAtt[.foregroundColor] = color
            let attStr = NSAttributedString(string: message, attributes: colorAtt)
            output.append(attStr)
        }
    }
    
    // 所有文字处理完成后,存储最终的文字
    let value: NSAttributedString
    // 从普通字符串初始化
    init(stringLiteral value: StringLiteralType) {
        self.value = NSAttributedString(string: value)
    }
    
    // 从带颜色的字符串初始化
    init(stringInterpolation: StringInterpolation) {
        self.value = stringInterpolation.output
    }
}

let str: ColorString = "asdfd\(message: "Red", color: .red)\(message: "Red", color: .red)\(message: "Blue", color: .blue)"
print(str.value)
var interploation = ColorString.StringInterpolation(literalCapacity: 1, interpolationCount: 1)
interploation.appendLiteral("111111")
interploation.appendInterpolation(message: "abc", color: .red)
interploation.appendLiteral("123")
let valentine = ColorString(stringInterpolation: interploation)
let valentine1 = ColorString(stringLiteral: "abc")
print(valentine.value)
print(valentine1.value.string)

不使用语法糖,也可以完成字符串的创建。

总结

自定义字符串插值,可以将格式代码封装在某个地方,使代码更简洁。同时,我们还可以创建一种原生的构件类型。
不过,这只是其中一种方式,你可以用插值法,也可以用函数,甚至其它方式。这需要根据具体情况来选择。

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