Swift 构造器

1,048 阅读13分钟

构造器

使用构造器来实现构造过程,构造器可以看做是用来创建新实例的特殊方法,与OC的构造器不同,之前是先调用父类的init再写自己的, 但是到了Swift里面, 我们却先初始化自己, 再初始化父类, 是相反的,swift中构造器无需返回值,主要任务是保证新实例在第一次使用前完成正确的初始化


为存储属性赋初始化值

  • 结构体创建实例时,必须为所有存储类型设置初始值
  • 可以在构造器中设置, 也可以在声明属性的时候就给定
  • 为存储属性设置默认值或在构造器中赋值时,它们的值是直接被设置的,不会触发任何属性观察者
定义一个不带参数的构造器init()
//类
class Student {
    var name:String
    var age = 12  //默认属性值(如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型)
    init() {
        name = "xiaoming" //将存储属性name的值初始化为12
    }
}

//结构体
struct People {
    var work:String
    init() {
        work = "teacher"
    }
}

自定义构造过程

通过输入参数和可选类型的属性来自定义构造过程

初始化参数

外部名字为stuAge、stuName,内部名字为ageInt、stuAge,将参数值保存在属性name、age中

class Student: NSObject {
    var age:Int
    var name:String
    init(stuAge ageInt:Int,stuName nameStr:String) {
        age = ageInt
        name = nameStr
    }
}
let stu = Student.init(stuAge: 12, stuName: "xiaoming")
参数的内部名称和外部名称

在定义构造器时没有提供参数的外部名字,Swift 会为构造器的每个参数自动生成一个跟内部名字相同的外部名,内部参数为ageInt、nameStr,在方法调用中可以看到外部参数也是ageInt、nameStr。

class Student: NSObject {
    var age:Int
    var name:String
    init(ageInt:Int,nameStr:String) {
        age = ageInt
        name = nameStr
    }
}
let stu = Student.init(ageInt: 12, nameStr: "xiaoming")
不带外部名的构造器参数

不希望为构造器的某个参数提供外部名字,你可以使用下划线 (_) 来显式描述它的外部名

class Student: NSObject {
    var name:String
    init(_ nameStr:String) {
        name = nameStr
    }
}
let stu = Student.init("xiaoming")
可选属性类型

如果定义的类型包含一个逻辑上允许取值为空的存储型属性,无论是因为它无法在初始化时赋值,还是因为它在之后某个时间点可以赋值为空,你都需要将它定义为可选类型。可选类型的属性将自动初始化为nil,表示这个属性是有意在初始化时设置为空的。

class Student: NSObject {
    var name:String
    var age:Int?  //自动赋值为nil
    init(_ nameStr:String) {
        name = nameStr
    }
}
let stu = Student.init("xiaoming")
stu.age = 12
print(stu.name,stu.age!)
构造过程中常量属性的修改

可以在构造过程中的任意时间点给常量属性赋值,只要在构造过程结束时是一个确定的值,一旦常量属性被赋值,它将永远不可更改。

class Student: NSObject {
    let age:Int
    init(_ ageInt:Int) {
        age = ageInt
    }
}
let stu = Student.init(12)

默认构造器

如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么系统会给结构体或类设置一个默认的构造器,这个默认构造器创建的实例对象,其对象的所有属性值都为其默认值

class Student: NSObject {
    var age:Int = 0
    var name:String?
    var hobby = ""
}

let stu = Student()
 //由于Student类中的所有属性都有默认值,它将自动获得一个可以为所有属性设置默认值的默认构造器(尽管代码中没有显式为name属性设置默认值,但由于name是可选字符串类型,它将默认设置为nil)。上面例子中使用默认构造器创造了一个Student类的实例,并将其赋值给常量stu。
结构体默认初始化

如果结构体没有提供自定义构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储属性没有默认值
逐一成员构造器通过与成员属性名相同的参数名进行传值来完成对成员属性的初始化赋值

struct Size {
    var w = 0.0
    var h = 0.0
}
let s = Size.init(w: 2.0, h: 2.0) //结构体Size自动获得了一个逐一成员构造器init(w:, h: )

值类型的构造器代理

  • 构造器可以通过调用其它构造器来完成实例的部分构造过程,这一过程称为构造器代理
  • 构造代理对值类型和引用类型来说不太一样, 值类型因为不支持继承, 所以只会用自己写的构造器来代理, 从而相对更简单. 类则会有从父类继承构造器的情况要考虑, 不过还是那句话, 所有存储属性在构造器中都完成初始化就可以.
  • 对于值类型,你可以使用self.init在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用self.init
  • 如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)
struct Size {
    var width = 0.0  // 全部有默认值, 会生成2个构造器
    var height = 0.0
}
struct Point {
    var x = 0.0
    var y = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    
    init(){}    //在功能上和系统默认构造器是一样的
    init(origin:Point,size:Size)//只是简单地将origin和size的参数值赋给对应的存储型属性
    {
        self.origin = origin
        self.size = size
    }
    init(center:Point,size:Size)//先通过center和size的值计算出origin的坐标,然后再调用(或者说代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中
    {
        let originX = center.x-(size.width / 2)
        let originY = center.y-(size.height / 2)
        // 构造器代理
        self.init(origin: Point.init(x: originX, y: originY), size: size)
    }
}

let rect1 = Rect.init()
let rect2 = Rect.init(center: Point.init(x: 1.0, y: 1.0), size: Size.init(width: 6.0, height: 6.0))
let rect3 = Rect.init(center: Point.init(x: 2.0, y: 2.0), size: Size.init(width: 6.0, height: 6.0))

类的构造过程

  • 类里面的所有存储型属性,包括所有继承自父类的属性,都必须在构造过程中设置初始值
  • Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器便利构造器
  • 指定构造器 是类中最主要的构造器。每个类至少有一个指定构造器,一个指定构造器必须真正完成所有存储属性的初始化,并根据父类链往上调用父类的构造器来实现父类的初始化
  • 便利构造器 是类中比较次要的。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值
指定构造器
class People: NSObject {
    var name:String
    init(name:String) {
        self.name = name
    }
}
便利构造器(在init关键字之前放置convenience关键字)
class People: NSObject {
    var name:String
    init(name:String) {
        self.name = name
    }
    convenience override init(){
        self.init(name: "xiaoming")
    }
}
类的构造器代理规则(类的指定构造方法和便利构造方法的相互调用规则)
  • 指定构造器必须调用其父类的指定构造器(指定构造器只能调用指定构造器)
  • 便利构造器必须调用同类中定义的其他构造器
  • 便利构造器必须最终导致一个指定构造器被调用

image.png

class Student {
    var age : Int
    init(){
        age = 10
    }
    convenience init(a:Int){
        self.init()
        age = a
        print(self)  // 打印两次, 第一次是Student, 第二次是Boy
    }
}

class Boy : Student {
    var name: String
    override init(){
        name = "boy"
        super.init()
    }
}

var stu = Student(a: 2)
var boy = Boy(a: 2)
两段式构造过程
  • 一个对象的内存只有在其所有存储型属性确定之后才能完全初始化
  • 类初始化有两阶段
    第一阶段,在类中的每个存储属性分配一个初始值
    第二阶段,每个类的实例在被使用之前进一步定义其存储的属性
第一阶段
• 某个指定构造器或便利构造器被调用
• 完成新实例内存的分配,但此时内存还没有被初始化
• 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化
• 指定构造器将调用父类的构造器,完成父类属性的初始化
• 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部
• 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化
第二阶段
• 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self修改它的属性并调用实例方法等等
• 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self
class Student {
    var type:String
    init() {
        type = "学生"
    }
}

class Boy: Student {
    //第一阶段:初始化存储属性
    var age = 0
    override init() {
        //第一阶段:初始化父类
        super.init()
        //第二阶段:子类自定义
        age = 10
    }
}
let s = Boy()
print(s.type) //学生
print(s.age)  //10
当你调用Boy()时
1.首先会调用super.init()初始化父类,这时Student类的属性在自身的指定构造器中被初始化完成
2.一旦父类初始化完成,就可以初始化子类的属性,并且可以子类定制属性,这里个性化设置age =10
  • Swift的编译器执行四个有用的安全检查,以确保完成两阶段初始化而不会出现错误:
    1.指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
    2.指定构造器必须先调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
    3.便利构造器必须先调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
    4.构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值

构造器的继承和重写

  • 跟 OC 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器,因为Swift不像OC会给属性默认值。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例
  • 如果子类的指派构造器和父类相同, 也要用override来修饰. 但是, 如果子类的指派构造器与父类的便利构造器相同, 那么父类的便利构造器永远都不会被子类调用到, 所以这种情况是不需要写override的.
构造器的自动继承
  • 子类在默认情况下不会继承父类的构造器,但是如果满足特定条件就可以

    • 如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器
    • 子类提供了全部的父类指定构造器而不是从情况1获得的, 即使是提供了一部分实现, 那么它将自动继承所有的父类便利构造器. (个人认为, 调用实现的那一个指派构造器的便利构造器都可以, 其余的不行可以更智能, 而且也不会出问题)
class ClassA {
    init(a:Int){
    }
    
    init(b:Float) {
    }
    
    convenience init(c:Int){
    }
    
    convenience init(d:Float){
    }
}

class ClassB: ClassA {
    override init(a: Int) {
        super.init(a: a)
    }
    override init(b: Float) {
        super.init(b: b)
    }
}

ClassB 中实现了 ClassA 中所有的指定构造器,那么在初始化 ClassB 对象的时候,选择方法如下

image.png

class ClassB: ClassA {
    override init(a: Int) {
        super.init(a: a)
    }
}

ClassB 中实现了 ClassA 中个别的指定构造器,那么在初始化 ClassB 对象的时候,选择方法如下

image.png


可失败构造器

  • 可失败构造器:有时候构造器需要返回失败,比如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件
  • 可失败的构造器声明:init?
    注意:可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同
  • 构造失败, 自然就是返回nil了, 所以可失败的构造器返回值是Optional的, 在使用的时候要注意拆包.

值类型和引用类型在处理失败构造的时候有些许不一样

  • 值类型的
struct Animal {
    let species:String
    init?(species:String) {
        if species.isEmpty{
            return nil
        }
        self.species = species
    }
}

let a = Animal.init(species: "giraffe")
print(type(of:a)) //Optional<Animal>


enum TemperatureUnit {
    case Celsius,Fahrenheit
    init?(symbol:Character){
        switch symbol {
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}

let t0 = TemperatureUnit.init(symbol: "F") //Fahrenheit
let t1 = TemperatureUnit.init(symbol: "T")  //nil
  • 类类型的(只有在所有的存储属性都赋予了初值之后才能出发失败构造)
class Product{
    let name:String!
    init?(name:String) {
        self.name = name
        if name.isEmpty {
            return nil
        }
    }
}
let p0 = Product.init(name: "")  //nil
let p1 = Product.init(name: "A")  //Product

构造失败的传递

类,结构体,枚举的可失败构造器可以传递到其他可失败构造器,而且子类的可失败构造器也能传递到父类的可失败构造器,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行

class ClassA{
    let str:String!
    init?(str:String) {
        if str.isEmpty{
            return nil
        }
        self.str = str
    }
}

class ClassB:ClassA{
    let b:Float
    init?(a:String,b:Float) {
        if b<1 {
            return nil
        }
        self.b = b
        super.init(str: a)
    }
}

let c = ClassB.init(a: "1", b: 0)   //nil
let d = ClassB.init(a: "", b: 1)   //nil
let e = ClassB.init(a: "1", b: 1)  //ClassB

重写可失败构造器

可以用一个不可失败的构造器重新父类可失败构造器,但是不能反过来,重写父类构造器时,返回值要强制解包

class ClassA{
    var str:String
    init?(str:String) {
        self.str = str
        if str.isEmpty{
            return nil
        }
    }
}

class classC:ClassA{
    var c:String
    override init(str: String) {
        c = str
        super.init(str: str)!
    }
}
let cc = classC.init(str:"d")  //classC

init!不可失败构造器

init?需要解包,init!直接使用
不管使用哪种形式,都需要判空


必要构造器

如果构造器用required来修饰,那么意味着子类必须重写该父类的构造器

class ClassA{
    var str:String
    required init(str:String){
        self.str = str
    }
}

class classC:ClassA{
    var c:String
    required init(str: String) {
        c = str
        super.init(str: c)
    }
}
let cc = classC.init(str: "c") //classC

如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现

class ClassA{
    var str:String
    required init(){
        str = "0"
    }
}

class classC:ClassA{
    var c = "1"
}
let cc = classC.init() //classC