本文系阅读阅读原章节后总结概括得出。由于需要我进行一定的概括提炼,如有不当之处欢迎读者斧正。如果你对内容有任何疑问,欢迎共同交流讨论。
如果你觉得这两节的内容比较杂乱,强烈推荐我的这篇总结: 你其实真的不懂print("Hello,world")
不知道你有没有注意到一个细节,不管你使用什么类型的参数,print
、String.init()
函数总是可以正常工作。比如我们定义一个结构体,其中年龄作为私有属性需要对外保密:
struct Person {
var name: String
private var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
分别使用print
和String.init()
函数,查看结果:
let kt = Person(name: "kt", age: 21)
let s: String = String(kt)
print(kt) // 输出结果:Person(name: "kt", age: 21)
print(s) // 输出结果:Person(name: "kt", age: 21)
好消息是这两个函数运行正常,打印出了结构体的所有信息,但缺点是私有属性也被显示出来了。如果想自定义输出格式,或避免暴露私有成员,也很容易实现,只需要实现CustomStringConvertible
协议即可:
extension Person: CustomStringConvertible {
var description: String {
return "Name is \(name)"
}
}
这样我们就可以完全自定义输出结果。调用print
或String.init()
函数将会得到Name is kt
。如果你有过Java编程的经验,你会发现这和toString
函数有异曲同工之妙。
如果只是专门用于调试,你还可以实现CustomDebugStringConvertible
协议:
extension Person: CustomStringConvertible, CustomDebugStringConvertible {
var debugDescription: String {
return "In debugging: name is \(name)"
}
}
为了使用这个协议,你可以调用String.init(reflecting: T)
方法,这个方法得到的字符串是T类型在实现CustomDebugStringConvertible
协议时定义的计算属性debugDescription
。举个例子说明:
let debug = String(reflecting: kt)
print(debug) // 输出结果:In debugging: name is kt
或者你也可以直接调用debugPrint
函数:
debugPrint(kt) // 输出结果:In debugging: name is kt
需要说明的是,即使你没有实现CustomDebugStringConvertible
协议,也依然可以使用debugPrint
和String.init(reflecting: T)
方法,此时的字符串会是CustomStringConvertible
协议中的计算属性description
。
由于实现了CustomStringConvertible
协议的类型一般都有很好的输出结果,你或许会实现这样的代码:
func doSomethingAttractive<T: CustomStringConvertible>(with value: T) {
// 可能会调用print方法输出value
}
如果你这么做了,很快你会发现String
类型并没有实现CustomStringConvertible
协议,而字符串恰好是最常被输出的类型。这是因为Swift不希望我们以这种方式使用CustomStringConvertible
协议。我们不应该去检查一个类型是否具有description
属性,而是应该不管在什么情况下都使用String.init
。我们也应该认识到,如果一个类型并不是可输出的,调用print
方法确实会得到一个很丑的结果。因此,除非是一个非常简单的类,我们总是应该实现CustomStringConvertible
协议,这用不了太多时间,但是会在以后的调试过程中起到很大的作用,正所谓磨刀不误砍柴工!