前言
本文主要是Swift的构造器相关知识,另加少部分的OC中的init方法(还有少许Java相关的构造方法),通过两者的对比来加深对Swift构造器的理解,毕竟Swift是要淘汰的OC的,所以主要研究Swift也是必然。
指定构造器和便利构造器
Swift构造器分为两类,一类是指定构造器(Designated Initializer),另一类是便利构造器(Convenience Initializer)。
指定构造器
指定构造器在一个类中必须至少有一个, 因为它是类的最主要构造器,没有之一,所有类的实例的初始化必然会调用指定构造器。以init开头,参数名、参数列表可以依照实际情况编写。当没有自定义指定构造器时,系统会自动生成一个不带参数的默认构造器,不带参数的默认构造器,意味着类的定义中,成员变量一定要赋上默认值。当有自定义指定构造器时,类的定义中,成员变量可以没有默认值,但是会在调用指定构造器时给其赋值,并且赋值时机要在调用父类的指定构造器之前(如果这个类有父类时)。举个例子,先定义一个基类。
```
class Person {
// 成员变量 name 、 sex都是没有赋值
var name:String
var sex:String
init(name:String, sex:String) {
// 在指定构造器中给成员变量赋值
self.name = name
self.sex = sex
}
}
```
在这个基类Person中,成员变量 name和sex在定义时都没有赋值,只是指定数据类型,相关的赋值是在指定构造器init(name:String, sex:String)中完成的。如果把Person类改成下面这样
```
// 这是个错误的定义
class Person {
var name:String
var sex:String
}
```
这个时候Xcode直接报错:
这里报错第一行显示是Person没有构造器,其他行显示name和sex没有初始值,其实本质来说,是因为成员变量在Swift中已经不是自动生成默认值,需要程序员自己指定。这里就需要提到一个不管是在Swift或者OC,还是Java或是其他面向对象的语言都有的概念:某个类的实例被创建,其成员变量一定要有值。在上面的错误定义中,因为Swift不再为成员变量自动生成默认值,如果程序员再不指定,这样在创建实例时成员变量会没有值,结果当然是直接就报错了。如果我们像下面这样稍微改动一下:
```
class Person {
var name:String="李磊"
var sex:String="男"
}
// 调用了Person自动生成的默认指定构造器(无参数)
var per = Person()
```
结果成功运行。在生成了per实例时,调用了自动生成的默认指定构造器,虽说是无参数,但是Person类在定义时,直接就给name和sex赋值了,所以这个per实例是成功被创建了。这里有一个不得不强调的一点,Swift的指定构造器本质是,确保本类的成员变量一定要被赋值,不是说一定要通过指定构造器来赋值。这个从上面改动的例子中可以看出(默认构造器并没有给成员变量赋值)。
当某个类有父类时,在其指定构造器中必须调用父类的指定构造器,且在调用父类的指定构造器前,必须得确保这个类的成员变量必须得有值。为证明这些,再定义一个Man类继承自Person类
```
class Man:Person {
var education:String="本科"
// age此处并没有被赋值
var age:Int
init(name:String, sex:String, age:Int) {
// 在调用父类的指定构造器之前,先给成员变量age赋值
self.age=age
// 调用父类的指定构造器
super.init(nameStr: name, sexStr: sex)
}
}
```
如果将上面的例子改成,在指定构造器中先调用父类的指定构造器,而后给成员变量age赋值,像下面一样
```
class Man:Person {
var education:String="本科"
// age此处并没有被赋值
var age:Int
init(name:String, sex:String, age:Int) {
// 先调用父类的指定构造器
super.init(nameStr: name, sexStr: sex)
// 再给成员变量age赋值
self.age=age
}
}
```
结果Xcode报错。
因为age在定义的位置并没有被赋值,所以在调用父类的指定构造器时age无值。对此强调一点:指定构造器在调用父类的构造器前,一定要确保子类引入的成员变量要有值。
便利构造器
由convenience关键字修饰,是横向代理。横向代理的意思是,convenience构造器中必须调用同一个类中的其他一个构造器,这个构造器是指定构造器或者便利构造器都行,但是,如果是便利构造器的话,convenience构造器通过调用链(代理链)最终都得调用一个designated构造器。举个例子,将Person和Man类稍微改一下:
```
class Person {
var name:String
var sex:String
init(name:String) {
self.name= name
self.sex="男"
}
//指定构造器可以有多个,但是至少有一个
init(nameStr:String, sexStr:String) {
self.name= nameStr
self.sex= sexStr
}
//定义便利构造器(使用convenience修饰)
convenience init(nameString:String, sexString:String) {
//这里的便利构造器必须调用同类中的指定构造器,因为Person是基类,便利构造器无法沿着构造器的调用链调用到父类指定构造器,因为基类是没有父类的,Swift不是OC,OC中NSObject是所有对象的基类,而Swift是没有这种“终极”基类的。
self.init(nameStr: nameString, sexStr: sexString)
}
convenience init(speakWord:String) {
self.init(nameStr:"人类", sexStr:"")
print("Person--\(speakWord)")
}
}
class Man:Person{
var education:String="本科"
var age:Int= 10
override init(name:String) {
//子类的指定构造器中必须调用父类的指定构造器
super.init(name: name)
}
override init(nameStr:String, sexStr:String) {
super.init(nameStr: nameStr, sexStr: sexStr)
}
init(name:String, sex:String, age:Int) {
self.age= 20
super.init(nameStr: name, sexStr: sex)
}
//定义指定构造器与父类的便利构造器一样,这里不算重写
convenience init(showStr:String, age:Int) {
//这里调用的是从父类继承来的便利构造器
self.init(speakWord:"hello!")
self.age= 20
print("Man---\(showStr)")
}
}
```
在改过后的Man类中,就不得不提构造器的继承。之前说过构造器默认是不继承,但是在有些情况下,会继承。
1.子类没有定义任何的指定构造器, 那么就会自动从父类那里继承所有的指定构造器
2.如果子类中提供了所有父类指定构造器,不管是通过规则1(没有定义任何指定构造器)继承来的,还是自定义实现的,它将继承所有父类的便利构造器
在改后的Man类中,将Person类中的指定构造器都overrider了,满足第二种情况,所以父类的便利构造器都被继承了。对于上面的规则1,个人觉得其实还可以改的更通俗点儿,只要是子类没有定义指定构造器,子类会从父类那里继承所有的构造器(包括便利构造器),有兴趣的可以试下将Man类其他的构造器都删掉,只保留便利构造器,在编写便利构造器时,会发现Person类的所有构造方法都已被继承。还有一点可以看出,在Man类的便利构造器中,调用的是从父类继承来的便利构造器( 这一句代码:self.init(speakWord:"hello!") ),在该便利构造器中又调用了“init(nameStr:String, sexStr:String)”这个指定构造器,所以调用便利构造器,最终都会沿着调用链调用到一个指定构造器。