Swift—文本輸出流

print是Swift標準庫中最經常使用的函數之一。實際上,這是程序員在編寫「Hello,world!」時學習的第一個函數。使人驚訝的是,咱們不多有人熟悉其餘形式。html

例如,您是否知道實際的簽名print是 print(_:separator:terminator:)?或者它有一個名爲print(_:separator:terminator:to:)?的變體 ?git

使人震驚,我知道。程序員

這就像瞭解你最好的朋友*「Chaz」* 的中間名,而且他的完整法定名稱其實是 「R」。巴克敏斯特小查爾斯拉格蘭德「 - 哦,並且,他們一直都有一個徹底相同的雙胞胎。github

一旦你花了一些時間來收集本身,請繼續閱讀,找出你以前認爲不須要進一步介紹的功能的所有真相。swift


讓咱們首先仔細看看以前的函數聲明:api

func print<Target>(_ items: Any...,
                   separator: String = default,
                   terminator: String = default,
                   to output: inout Target)
    where Target : TextOutputStream

複製代碼

這個重載print 採用可變長度的參數列表,後跟separatorterminator參數 - 二者都有默認值。bash

  • separator是用於將每一個元素的表示鏈接items 成單個字符串的字符串。默認狀況下,這是一個空格(" ")。
  • terminator是附加到打印表示的末尾的字符串。默認狀況下,這是換行符(\ n "\n")。

最後一個參數output 採用Target符合協議的泛型類型的可變實例。Text<wbr style="box-sizing: border-box;">Output<wbr style="box-sizing: border-box;">Streamapp

符合的類型的實例 能夠傳遞給函數以從標準輸出中捕獲和重定向字符串。Text<wbr style="box-sizing: border-box;">Output<wbr style="box-sizing: border-box;">Stream``print(_:to:)框架

實現自定義文本輸出流類型

因爲Unicode的多變性,您沒法經過查看字符串來了解字符串中潛伏的字符。在 組合標記, 格式字符, 不支持的字符, 變體序列, 連字,有向圖和其餘表現形式之間,單個擴展字形集羣能夠包含遠遠超過眼睛的東西。ide

舉個例子,讓咱們建立一個符合的自定義類型。咱們不會逐字地將字符串寫入標準輸出,而是檢查每一個組成代碼點Text<wbr style="box-sizing: border-box;">Output<wbr style="box-sizing: border-box;">Stream

符合協議只是知足方法要求的問題。Text<wbr style="box-sizing: border-box;">Output<wbr style="box-sizing: border-box;">Stream``write(_:)

protocol TextOutputStream {
    mutating func write(_ string: String)
}

複製代碼

在咱們的實現中,咱們迭代Unicode.Scalar傳遞的字符串中的每一個值; 的enumerated()收集方法提供當前在每次循環偏移。在方法的頂部,guard若是字符串爲空或換行符,則語句會提早解除(這會減小控制檯中的噪音量)。

struct UnicodeLogger: TextOutputStream {
    mutating func write(_ string: String) {
        guard !string.isEmpty && string != "\n" else {
            return
        }

        for (index, unicodeScalar) in
            string.unicodeScalars.lazy.enumerated()
        {
            let name = unicodeScalar.name ?? ""
            let codePoint = String(format: "U+%04X", unicodeScalar.value)
            print("\(index): \(unicodeScalar) \(codePoint)\t\(name)")
        }
    }
}

複製代碼

要使用咱們的新類型,請初始化它並將其分配給變量(with ),以便它能夠做爲參數傳遞。任什麼時候候咱們想要得到字符串的X射線而不是僅僅打印它的表面表示,咱們能夠在咱們的聲明中添加一個額外的參數。Unicode<wbr style="box-sizing: border-box;">Logger``var``inout``print

這樣作可讓咱們揭示關於表情符號字符的祕密👨👩👧👧:它其實是 由ZWJ字符加入的四個單獨表情符號的 序列 - 總共七個代碼點!

print("👨‍👩‍👧‍👧")
// Prints: "👨‍👩‍👧‍👧"

var logger = UnicodeLogger()
print("👨‍👩‍👧‍👧", to: &logger)
// Prints:
// 0: 👨 U+1F468    MAN
// 1:    U+200D     ZERO WIDTH JOINER
// 2: 👩 U+1F469    WOMAN
// 3:    U+200D     ZERO WIDTH JOINER
// 4: 👧 U+1F467    GIRL
// 5:    U+200D     ZERO WIDTH JOINER
// 6: 👧 U+1F467    GIRL

複製代碼

在Swift 5.0中,您能夠經過其Unicode properties屬性訪問標量值的名稱。與此同時,咱們可使用 字符串變換 來爲咱們提取名稱(咱們只須要在兩端去掉一些殘骸)。

import Foundation

extension Unicode.Scalar {
    var name: String? {
        guard var escapedName =
                "\(self)".applyingTransform(.toUnicodeName,
                                            reverse: false)
        else {
            return nil
        }

        escapedName.removeFirst(3) // remove "\\N{"
        escapedName.removeLast(1) // remove "}"

        return escapedName
    }
}

複製代碼

有關更多信息,請參閱 SE-0211:「將Unicode屬性添加到Unicode.Scalar」

使用自定義文本輸出流的想法

如今咱們知道Swift標準庫的一個不起眼的部分,咱們能夠用它作什麼?

事實證實,有不少潛在的用例。爲了更好地瞭解它們是什麼,請考慮如下示例:Text<wbr style="box-sizing: border-box;">Output<wbr style="box-sizing: border-box;">Stream

記錄到標準錯誤

默認狀況下,Swift print語句指向 標準輸出(stdout。若是您但願改成指向 標準error(stderr,則能夠建立新的文本輸出流類型並按如下方式使用它:

import func Darwin.fputs
import var Darwin.stderr

struct StderrOutputStream: TextOutputStream {
    mutating func write(_ string: String) {
        fputs(string, stderr)
    }
}

var standardError = StderrOutputStream()
print("Error!", to: &standardError)

複製代碼

將輸出寫入文件

前面的寫入示例stderr 能夠歸納爲寫入任何流或文件,而是經過建立輸出流 (能夠經過類型屬性訪問標準錯誤)。File<wbr style="box-sizing: border-box;">Handle

import Foundation

struct FileHandlerOutputStream: TextOutputStream {
    private let fileHandle: FileHandle
    let encoding: String.Encoding

    init(_ fileHandle: FileHandle, encoding: String.Encoding = .utf8) {
        self.fileHandle = fileHandle
        self.encoding = encoding
    }

    mutating func write(_ string: String) {
        if let data = string.data(using: encoding) {
            fileHandle.write(data)
        }
    }
}

複製代碼

按照這種方法,您能夠自定義print寫入文件而不是流。

let url = URL(fileURLWithPath: "/path/to/file.txt")
let fileHandle = try FileHandle(forWritingTo: url)
var output = FileHandlerOutputStream(fileHandle)

print("\(Date())", to: &output)

複製代碼

轉發流輸出

做爲最後一個例子,讓咱們想象一下你會發現本身常常將控制檯輸出複製粘貼到某個網站上的表單中的狀況。不幸的是,該網站有試圖解析無益的行爲<,並>就好像它們是HTML。

每次發佈到網站時,您均可以建立一個 自動處理該文本的內容,而不是採起額外的步驟來逃避文本(在這種狀況下,咱們使用咱們發現深埋在Core Foundation中的XML轉義函數)。Text<wbr style="box-sizing: border-box;">Output<wbr style="box-sizing: border-box;">Stream

import Foundation

struct XMLEscapingLogger: TextOutputStream {
    mutating func write(_ string: String) {
        guard !string.isEmpty && string != "\n",
            let xmlEscaped = CFXMLCreateStringByEscapingEntities(nil, string as NSString, nil)
        else {
            return
        }

        print(xmlEscaped)
    }
}

var logger = XMLEscapingLogger()
print("<3", to: &logger)
// Prints "&lt;3"

複製代碼

對於開發人員來講,打印是一種熟悉且便捷的方式,能夠了解其代碼的行爲。它補充了更全面的技術,如日誌框架和調試器,而且 - 在Swift的狀況下 - 證實它自己就很是強大。


掃碼進交流羣 有技術的來閒聊 沒技術的來學習

691040931

原文轉載地址:nshipster.com/textoutputs…

相關文章
相關標籤/搜索