构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个存储型属性设置初始值和为其执行必要的准备和初始化任务。
构造过程是通过定义构造器(Initializers
)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类,结构体和枚举都有一个称为构造器的东西,这个东西帮我们完成初始化工作.其实构造器是一个名叫init的方法,这个方法可以有参数列表,但是没有返回值.在方法体里面进行所有的初始化工作,例如:给每个存储属性设置一个初始值,完成所有初始化有关的工作.
//先看看构造语法的格式:
init(形参列表){ //注意:没有定义返回值
//方法体进行初始化的一系列操作
}
class Buddha{
var name: String
init (name: String){ //定义一个构造器
self.name = name
}
}
var buddha = Buddha(name: "guanyin")//使用构造器
/*
使用构造器跟使用其他普通的方法有很大的区别,它不会直接使用方法名init去做调用,而直接使用类名进行调用,经过这个调用以后就会生成一个初始化过程.
构造器的使用语法:
var 变量名 = 类名(实参列表)
*/
存储属性的初始化
Swift规定所有的变量在使用之前都必须被初始化.对于 swift 的内建类型(Int,String,Double等),这意味着变量在声明时就必须赋初始值.而对于枚举,结构体,类等自定义类型,为了保证我们安全的使用,在使用之前,必须初始化所有的存储属性.说白了就是保证我们的引用(变量)总是指向已经存在的完整的内存单元.
下图展示了存储属性不能不被初始化的事实:
有以下两种方法完成存储属性的初始化:
//1)在声明属性的同时提供初始化值.
class People{
var appearance: String = "" //存储属性,初始化为""
var lover: People? //存储属性,初始化为nil.因为对于可选类型,在没有给定初始值的时候, Swift会自动给设置为nil,所以lover的初始值为 nil.
}
//2)像之前的例子,使用init构造器方法,在这个方法里面设置初始化的步骤.
/*
init 构造器有三点需要说明一下:
1:参数列表里面可以给多个参数,也可以一个参数也没有.
2:支持重载和重写.
3:没有返回值.
*/
//下面来看多个参数的构造器:
class People{
var name: String //姓名,由于在init里面初始化,这里没有给默认值.
var hometown: String //家乡
var age: Int = 0 //年龄
var gender: String //性别
init(name: String,hometown: String,age: Int,gender: String){ //构造器
self.name = name
self.hometown = hometown
self.age = age
self.gender = gender
print("我叫\(self.name),我来自于\(self.hometown),今年\(self.age)岁,性别\(self.gender)")
//我叫jack,我来自于湖南郴州,今年23岁,性别Male
}
}
let jack = People(name: "jack", hometown: "湖南郴州", age: 23, gender: "Male")
存储属性初始化的注意事项:1:在调用构造器时,通过构造器中的参数名和类型来确定需要调用的构造器.
2:由于调用构造器的时候没有方法名,参数就是区分调用哪个构造器的主要凭据,所以参数很重要,如果在定义构造器时没有提供外部参数名, swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名称,就相当于在每个构造参数之前加了一个"#"符号.
3:如果不想为构造器的某个参数提供外部名称,可以通过在参数前面加"_"来覆盖上一点提到的默认行为.
4:默认值和init构造器的初始化存储属性的两种方式,任何一个存储属性在创建的时候都必须使用其中一种来初始化.
//结合例子学习一下:"挖掘机哪家强,老码改行去蓝翔!"
//定义蓝翔收费标准的枚举类型,注意"可失败构造器"在枚举类型中的使用.
enum LanXiangFee{
case XiJian
case WanJue
//这是一个可失败的构造器,表示钱不够时返回nil 以表示实例构造失败.
init?(money: Int){
if money >= 30000 && money < 50000{
self = .XiJian
}else if money >= 50000{
self = .WanJue
}else{
print("拿着\(money)就想上蓝翔,对得起唐国强老师吹下的牛吗!")
//注意:在5大类构造器中只有可失败构造器可以返回值,并且返回值只能够为nil.
return nil
}
}
}
//定义学员的结构体,注意:"成员构造器和默认构造器"在结构体中使用
struct Student{
var name:String = "路人甲"
var field:String = "挖掘机"
var money = 30000
}
//定义技校类 便捷构造器,指定构造器,可失败构造器
class TechnicalSchool{
var name: String?
var location: String?
var topField: String?
var advertisement: String?
//这是指定构造器
init(name:String ,location: String,topField: String,advertisement: String){
self.name = name
self.location = location
self.topField = topField
self.advertisement = advertisement
}
//这是一个便捷构造器
convenience init(){
self.init(name:"山东蓝翔技校" ,location: "山东",topField: "挖掘机",advertisement: "挖掘机哪家强,快去山东找蓝翔!")
}
//可失败构造器
init?(has3IDCard: Bool){
return nil
}
//便捷失败构造器 这里调用了便捷构造器,因此该构造器也是便捷构造器,所以凡是调用了指定构造器的可失败构造器,我们都称其为便捷失败构造器.
convenience init!(isRumor:Bool){
self.init(name:"山东蓝翔技校" ,location: "山东",topField: "挖掘机",advertisement: "挖掘机哪家强,快去山东找蓝翔!")
}
func receive(student: Student){
print("\(name) 欢迎你!")
}
}
func testThree(){
//使用结构体成员构造器生成一个Student实例
var oldCoder = Student(name: "jack", field: "洗剪吹", money: 250)
//在路上遇到了一个蓝翔挖掘机的学院路人甲(使用结构体默认生成一个Student实例student1)
var student1 = Student()
//扎到了蓝翔技校(使用类的便捷构造器生成lanXiangTechnicalSchool实例)
var lanXiangTechnicalSchool = TechnicalSchool()
//找到了老罗技校(使用类的指定构造器生成laoLuoTechnicalSchool实例)
var laoLuoTechnicalSchool = TechnicalSchool(name: "北京老罗技校", location: "北京", topField: "锤子挖掘机", advertisement: "学好挖掘机,还得靠3倍情怀!")
var feeForOldCoder = LanXiangFee(money: oldCoder.money)
if feeForOldCoder == nil{
print("蓝翔拒绝了你,请找老罗!")
laoLuoTechnicalSchool.receive(oldCoder)
}else{
lanXiangTechnicalSchool.receive(oldCoder)
}
//因为3个身份证的问题,蓝翔技校可能被关闭(使用可失败构造器触发实例构造失败)
var lanXiangTechnicalSchoolNow = TechnicalSchool(has3IDCard: true)
if lanXiangTechnicalSchoolNow == nil{
print("无法生成lanXiangTechnicalSchoolNow实例")
}
//谣言已过,蓝翔技校开张(使用可失败构造器保证实例构造成功)
lanXiangTechnicalSchoolNow = TechnicalSchool(isRumor: true)
if lanXiangTechnicalSchoolNow != nil{
print("生成了lanXiangTechnicalSchoolNow实例")
}
/*
拿着250就想上蓝翔,对得起唐国强老师吹下的牛吗!
蓝翔拒绝了你,请找老罗!
Optional("北京老罗技校") 欢迎你!
无法生成lanXiangTechnicalSchoolNow实例
生成了lanXiangTechnicalSchoolNow实例
*/
}
在Swift 的世界中总共有5类构造器,分别用于类,结构体和枚举类型中,分别如下:
默认构造器:该构造器适用于类,结构体和枚举类型,不需要实现, Swift 编译器会自动生成.在前面例子可以看到,直接通过调用Student()构造方法生成student1这个实例,不管对于枚举,结构体还是类类型,只要自己不编写构造器方法, Swift编译就会默认生成一个构造器,这就是默认构造器,使用默认构造器要保证当前类型中的所有的属性类型确定,否则会直接报错.
指定构造器:该构造器只适用于类类型,前面例子中用的init构造器大部分是指定的构造器.指定构造器是类中最主要的构造器,一个指定构造器将初始化类中通过的所有属性,并根据父类链网上调用父类的构造器来实现父类的初始化.每个类都拥有至少一个指定的构造器,指定构造器必须总是向上代理(即调用父类的构造器).通俗一点讲:指定构造器就是用来保证从父类继承的属性及本类的属性一个都不能够少地都被初始化,通常在这种构造器里面大家可以看到 super.init()这样的语句,用来调用父类的指定构造器.
便捷(便利)构造器:该构造器适用于类类型,它主要是为了简化指定构造器的使用而增加的, Swift语言里通过指定构造器和便捷构造器来解决复杂继承的初始化问题.
指在构造器方法 init前增加convenience关键字修饰,便利构造器是类中比较次要的构造器,你可以定义便利构造器来调用一个类中的指定构造器,并为其参数提供默认值,也可以定义便利构造器来创建一个特殊用途或者特定输入的实例.便利构造器必须是横向代理(及调用本类的其他构造器).通常可以看到self.init这样的语句出现在便利构造器里面,这样的调用是为了在某种情况下通过使用便利构造器来快捷调用某个指定构造器,哪个节省更多开发时间并让类的构造过程更加清晰明了.
成员构造器:该构造器只适用于结构体类型,方便初始化结构体成员.我们可以直接把结构体成员放到参数列表,并且不需要事先定义一个具有参数的列表构造方法.
可失败构造器:该结构体适用于类,结构体和枚举类型,它主要用在某类对象构造时可能失败的情况下.可失败构造器的格式为:[convenience] init?/init!(参数列表){//构造器实体}.
因为可失败构造器其实返回一个可选类型,所以根据可选类型的特性,可失败构造器又分为两种形式init?和 init!,方便表示当前实例可能造成失败的情况以及一定构造成功的情况.其中init!相当于可选类型的解封操作,因此也称为"隐式解封可失败构造器".
注意:
前面我们在枚举和类中都使用了可失败构造器,我们知道可失败构造器用于枚举类型和结构体类型是值类型,可失败构造器用于类类型即是引用类型.这里有一个重要区别:那就是值类型可失败构造器可以在构造器的任意位置返回nil,表示构造失败.但是对于引用类型着必须等到当前类的存储属性初始化完成后才能够返回nil.
类的构造器代理规则
使用规则如下:
1):指定构造器必须调用其父类的指定构造器.
2):便利构造器必须调用同一类中定义的其他构造器.
3):便利构造器必须最终以调用一个指定构造器结束.
一个更方便记忆的方法是:
指定构造器必须总是向上代理.
便利构造器必须总是横向代理.
这些规则可以通过下面图例来说明:
如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。这满足了上面提到的规则2和3。这个父类没有自己的父类,所以规则1没有用到。
子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一个类里的其他构造器。这满足了上面提到的规则2和3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则1。
注意:
这些规则不会影响使用时,如何用类去创建实例。任何上图中展示的构造器都可以用来完整创建对应类的实例。这些规则只在实现类的定义时有影响。
下面图例中展示了一种针对四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系:
现在对于指定构造器和便利构造器有了一定的理解,接下里具体看看构造器是如何构造我们类的实例, Swift采用和OC相同的"两段式"初始化.
第一阶段,每个存储属性通过引入它们的类的构造器来设置初始值,具体流程如下:
1:某个指定构造器或者便利构造器被调用.
2:完成新实例内存的分配,但此时内存还没有初始化.
3:指定构造器确保其所在类引入的所有存储属性都已赋初始值,存储型属性所属的内存初始化完成.
4:指定构造器将调用父类的构造器,完成父类属性的初始化(这里需要显示的调用super.init(),如果没有显示调用,则在当前构造器结束后,有 Swift 默认调用.)
5:这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器的最顶部.
6:当到达构造器链最顶部,并且已确保所有实例包含的存储属性都已经,这个实例的内存被认为完全初始化,此时阶段1完成.
第二阶段:它给每一个类一次机会在新实例准备使用之前进一步定制它们的存储属性.
1:从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例,构造器此时可以访问self修改它的属性并调用实例方法.
2:最终,任意构造器链中的变量构造器可以有机会定制实例和使用self.
下面看个例子:
//在这里我们定义了四个类,在整个继承层次中,Base1是最基本的父类,其他类因此继承.
class Base1{
var name1: String
init(){
self.name1 = "曾祖父"
print("this is init of \(self.name1)")
}
}
class Base2: Base1{
var name2: String
override init(){
self.name2 = "Base2"
print("this is init of \(self.name2)")
}
}
class Base3: Base2{
var name3: String
override init() {
self.name3 = "Base3"
print("this is init of \(self.name3)")
}
}
class Base4: Base3{
var name4: String
init(name: String) {
self.name4 = name
print("this is init of \(self.name4)")
}
}
func testFour(){
var inheritTest = Base4(name: "Base4")
/*
打印结果:
this is init of Base4
this is init of Base3
this is init of Base2
this is init of 曾祖父
分析:
上面的代码, Swift是为我们这么构造的:
1:调用Base4的构造器.
2:因为没有显示地调用父类的构造器,所以Swift会在Base4的构造器调用结束后,自动为我们调用Base3的构造器,而在Base3构造器调用结束后,
Swift同样为我们调用Base2的构造器,依次进行.调用顺序Base4->Base3->Base2->Base1.
3:注意:只有super构造器被调用后,我们才能够使用父类定义的属性,所以下面操作是错误的:图一. 如果我们修改一下就可以了,如图二.
因为super.init()会顺序往上调用所有父类的构造器,之后,我们就可以按照我们的想法重新修改父类的属性值.
*/
}
图一:
图二:
构造器的重写
当你写一个父类中带有指定构造器的子类构造器时,你需要重写这个指定的构造器。因此,你必须在定义子类构造器时带上override修饰符.
无论是重写属性,方法或者是下标脚本,只要含有override修饰符就会去检查父类是否有相匹配的重写指定构造器和验证重写构造器参数。
//定义了一个基础类叫Vehicle
class Vehicle{
//Vehicle类只为存储型属性提供默认值,而不自定义构造器。因此,它会自动生成一个默认构造器.
var numberOfWheels = 0
var des:String{
return "\(numberOfWheels) wheel(s)"
}
}
//子类Bicycle定义了一个自定义指定构造器init()。这个指定构造器和父类的指定构造器相匹配,所以Bicycle中的指定构造器需要带上override修饰符.
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
//注意:子类可以在初始化时修改继承变量属性,但是不能修改继承过来的常量属性。
}
}
func testFive(){
let vehicle = Vehicle()
print("Vehicle: \(vehicle.des)")
// Vehicle: 0 wheel(s)
let bicycle = Bicycle()
print("Bicycle: \(bicycle.des)")
// Bicycle: 2 wheel(s)
}
必要构造器
在类的构造器前添加 required 修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
required init() {
// 在这里添加该必要构造器的实现代码
}
}
当子类重写基类的必要构造器时,必须在子类的构造器前同样添加required修饰符以确保当其它类继承该子类时,该构造器同为必要构造器。在重写基类的必要构造器时,不需要添加override修饰符:
class SomeSubclass: SomeClass {
required init() {
// 在这里添加子类必要构造器的实现代码
}
}
注意:如果子类继承的构造器能满足必要构造器的需求,则你无需显示的在子类中提供必要构造器的实现。
通过闭包和函数来设置属性的默认值
如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。
下面列举了闭包如何提供默认值的代码概要:
class SomeClass {
let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值
// someValue 必须和 SomeType 类型相同
return someValue
}()
}
//注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,
//相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
注意:如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的self属性,或者调用其它的实例方法。