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

現(xiàn)在Swift Evolution 不斷推送Swift向前發(fā)展,Swift4.2中就引入了很多功能。
Swift5.0中,ABI穩(wěn)定性是重點,字符串插值也獲得了超級功能,我們可以更好的控制它。
Swift5.0中新的字符串插值系統(tǒng),我們可以擴展String.StringInterpolation來添加我們的自定義插值

// 覆蓋了協(xié)議方法
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的默認值,因此需要命名參數來避免混淆。

// 通過添加命名參數來區(qū)分默認的方法
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)
    }
}

帶插值的構建類型

如上所述,插值是一種非常干凈的控制數據格式化的方式。
我們也可以通過插值來構建自己的類型。

我們將使用字符串插值方法來創(chuàng)建帶顏色的字符串的類型。

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]
        // 必須的,創(chuàng)建時可用于優(yōu)化性能
        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)

不使用語法糖,也可以完成字符串的創(chuàng)建。

總結

自定義字符串插值,可以將格式代碼封裝在某個地方,使代碼更簡潔。同時,我們還可以創(chuàng)建一種原生的構件類型。
不過,這只是其中一種方式,你可以用插值法,也可以用函數,甚至其它方式。這需要根據具體情況來選擇。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容