每天了解一点不一样的 Swift 小知识

代码截图

image.png

小笔记

这段代码在说什么

这段代码为 Bool 类型进行了扩展,并利用扩展为现有的 Bool 类型添加了一个新的构造器:init(input: String)。当输入参数为 y, yes, 👍 的时候,构造出来的实例值为 true,其余情况则为 false。

值类型和类类型的构造过程

我们都知道,构造器可以通过调用其它构造器来完成实例的部分构造过程,这一过程被称为构造器代理(initializer delegation),这种模式能避免多个构造器间的代码重复。

但是构造器代理的实现规则和组织形式在值类型(value type)和类类型(class type)中有所不同。

值类型是不支持继承的,例如枚举和结构体,所以它们的构造器代理过程相对简单,因为它们只能代理给自己的其它构造器。

类则不同,它可以继承自其它类,这意味着类要确保所有继承下来的存储型属性在构造时能被正确的初始化,这也是为什么类类型在构造过程中有两段式的构造过程,要遵守构造器代理的三个原则,并进行四种构造安全检查

说了这么多,回到今天的代码上!

通过前面的讲解,我们应该回想起了值类型与类类型在构造过程中的差异,想必这时候的大家,对于代码截图中能直接使用 self,而不需要调用 super.init() 等操作也都能理解了。

如果还是有很多疑惑,不妨重温一下官方手册里关于构造过程的章节

让 Struct 构造器变得更好用一点

在一些特定的场景下,Swift 能够为 Struct 类型生成一个默认构造器,也可以叫它为逐一成员构造器(memberwise initializer),例如下面的代码,Swift 就为 myStruct 结构体创建了一个构造器:init(myString: String?, myInt: Int?, myDouble: Double?)

struct myStruct {
let myString: String?
let myInt: Int?
let myDouble: Double?
}
image.png

虽然这个自动生成的默认构造器看起来中规中矩,也帮我们省去了不少打字的功夫,但在每次使用的时候,我们不得不完整的写出三个参数,即使这个参数是没有值的,这确实有点麻烦!

let aStruct = myStruct(myString: "1", myInt: nil, myDouble: nil)

那么有什么好的技巧来解决这个问题呢?

答案就是重新声明整个构造函数并在构造函数的每个参数后面添加默认值为 nil 的逻辑!

struct myStructWithInit {
let myString: String?
let myInt: Int?
let myDouble: Double?
init(myString: String? = nil, //👈
myInt: Int? = nil,
myDouble: Double? = nil) {
self.myString = myString
self.myInt = myInt
self.myDouble = myDouble
}
}

此时,我们再次创建 myStructWithInit 类型的实例,会发现代码提示里的构造函数变成了两个!一个是我们之前见过的样式,另一个是没有任何参数的样式。

image.png

不过,这并不意味着你只有 2 个构造方法可以用哦,现在的你也可以用下面的方式来构造实例了!

myStructWithInit(myString: "Something")
//or
myStructWithInit(myString: "Something", myInt: 2)
//or
myStructWithInit(myString: "Something", myInt: 2, myDouble: 3.0)

惊不惊喜,意不意外!现在我们终于可以自由的使用构造函数啦!

之所以能这样做,是因为当我们为函数的某个参数设置了默认值后,再调用该函数时,就可以忽略它。这个知识点可以从官方手册里的默认函数值一节中找到。

所以,今天你学会如何通过默认参数值这个技巧来创建更灵活,更自由,更方便的结构体构造函数了么?