2024-08-20 16:59:44
,某些文章具有时效性,若有错误或已失效,请在下方留言。有什么问题?
print()
方法有一定的缺点,为了说明这个问题,我们定义一个 Footballer
结构体,结构体包含队员的姓名、位置以及俱乐部,后边的两者是不同的类型。
enum Position {
case goal, defence, midifield, attack
}
struct Club {
var name: String
init(name: String) {
self.name = name
}
}
struct Footballer {
var name: String
var club: Club
var position: Position
init(name: String, club: Club, position: Position) {
self.name = name
self.club = club
self.position = position
}
}
我们现在可以继续使用这个类型
let richmond = Club(name: "AFC Richmond")
let roy = Footballer(name: "Roy Kent", club: richmond, position: .midifield)
print(roy)
打印的结果如下
Footballer(name: “Roy Kent”, club: PrintingData.Club(name: “AFC Richmond”), position: PrintingData.Position.midifi
如果将 Footballer 定义为类,而非结构体
class Footballer {
打印的输出内容如下
PrintingData.Footballer
介绍 dump()
Swift 有几种将信息打印到调试输出的方法,但真正最有用的方法是 dump()
,因为它使用对象的 镜像 来确定对象所包含的内容。
镜像 允许对 Swift 类型进行自省,这意味着当我们创建一个类型的镜像时,我们可以读取该类型中的所有属性名和值–我们可以对属性及其值进行字面上的循环,这正是 dump()
所要做的。
更妙的是,dump()
会递归遍历我们要转储的类型中的所有类型,因此我们可以得到一个非常漂亮的层次结构。
当 Footballer
为类时,将 print
变为 dump
,如下
dump(roy)
打印的内容,如下
▿ PrintingData.Footballer #0
- name: "Roy Kent"
▿ club: PrintingData.Club
- name: "AFC Richmond"
- position: PrintingData.Position.midifield
关于其它类型
这种额外的信息非常受欢迎,在许多其他类型中也会出现。
print(1...10)
dump(1...10)
打印的内容,如下
1...10
▿ ClosedRange(1...10)
- lowerBound: 1
- upperBound: 10
你会发现 dump()
更加清晰:它会打印出我们正在使用的类型,并说明这两个值的含义。
对数组来说也更清晰:
let names = ["Taylor", "Justin", "Adele"]
print(names)
dump(names)
打印的内容,如下
["Taylor", "Justin", "Adele"]
▿ 3 elements
- "Taylor"
- "Justin"
- "Adele"
你会看到 dump()
会打印出数组的计数,并将每个元素放在自己的一行中。
它还能为 Foundation
类型提供 print()
无法提供的信息。例如,尝试使用 IndexSet
:
let numbers = IndexSet([1, 2, 3, 5, 7, 9])
print(numbers)
dump(numbers)
打印的内容,如下
6 indexes
▿ 6 indexes
▿ ranges: 4 elements
▿ Range(1..<4)
- lowerBound: 1
- upperBound: 4
▿ Range(5..<6)
- lowerBound: 5
- upperBound: 6
▿ Range(7..<8)
- lowerBound: 7
- upperBound: 8
▿ Range(9..<10)
- lowerBound: 9
- upperBound: 10
print()
只会告诉你 IndexSet
中有多少个数字,而 dump()
则会细分实际内容。
自定义输出
我们可以向 dump()
传递一些参数,以自定义其工作方式。
最简单的是 name
参数,它允许我们为被转储的内容添加一个字符串名称,以方便识别:
dump(roy, name: "Roy")
打印的内容,如下
▿ Roy: PrintingData.Footballer #0
- name: "Roy Kent"
▿ club: PrintingData.Club
- name: "AFC Richmond"
- position: PrintingData.Position.midifield
我想说的后两个是 maxDepth
和 maxItems
。这两个是一起工作的:
maxDepth
决定dump()
将导航到子对象的哪个深度。例如,如果将 roy 对象的最大深度设为 1,就意味着将打印其属性,但 dump() 不会打印其俱乐部的 name 属性。maxItems
决定打印的属性总数。这是所有对象的总计数,递归计算。
我们可以将 maxDepth
设为 2,将 maxItems
设为 4:
dump(roy, name: "Roy", maxDepth: 2, maxItems: 4)
打印的内容,如下
▿ Roy: PrintingData.Footballer #0
- name: "Roy Kent"
▿ club: PrintingData.Club
- name: "AFC Richmond"
(1 more child)
这将打印 Roy 以及他的姓名、俱乐部和俱乐部名称,但不会打印他的位置,因为已达到 maxItems
。
不过,最有趣的参数是我们把转储的数据写到哪里,print()
和 dump()
都有一个额外的参数,可以将它们的输出引向其他地方–任何知道如何接受字符串并以某种方式处理它的地方。
class Logger: TextOutputStream {
private let fileHandle: FileHandle
init(url: URL) throws {
// more code to come
}
func write(_ string: String) {
let data = Data(string.utf8)
fileHandle.write(data)
}
deinit {
try? fileHandle.close()
}
}
这会将所有输入字符串写入一个打开的文件句柄。其中唯一真正的工作是在文件不存在的情况下创建文件,然后将其赋值给 fileHandle
属性。
将 // more code to come 替换为以下内容:
let fm = FileManager.default
let path = url.path()
if fm.fileExists(atPath: path) == false {
fm.createFile(atPath: path, contents: nil)
}
fileHandle = try FileHandle(forWritingTo: url)
try fileHandle.seekToEnd()
现在,我们可以像这样直接将数据打印或转储到文本文件中:
let url = URL.homeDirectory.appending(path: "output.log")
var logger = try Logger(url: url)
dump(roy, to: &logger, name: "Roy", maxDepth: 2, maxItems: 4)
output.log
日志文件内容
参考
- 文章来自: Hacking With Swift Plus ↩
暂无评论内容