Swift各种构造器

176 阅读8分钟

构造器

使用构造器来实现构造过程,构造器可以看做是用来创建新实例的特殊方法。

构造过程:是使用类、结构体或者枚举类型的实例之前的准备过程。在新的实例可用之前必须执行这个过程,具体操作包括设置实例中每个储存类型属性的初始值和执行其它必须设置或初始化的工作。

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

默认构造器

如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么swift会给这些结构体或类提供一个默认构造器。这个默认的构造器将简单的创建一个所有属性值都设置为默认的实例。

结构体的逐一成员构造器

结构体如果没有定义任何自定义构造器,它们将自动获得一个逐一成员构造器。

struct AA {
    var a = 1
    var b = "2"
    var c = 3.0
}

image.png

值类型的构造器代理

  • 构造器可以通过调用其它构造器来完成实例的部分构造过程,这一过程称为构造器代理
  • 构造代理对值类型和引用类型来说不太一样, 值类型因为不支持继承, 所以只会用自己写的构造器来代理, 从而相对更简单. 类则会有从父类继承构造器的情况要考虑, 不过还是那句话, 所有存储属性在构造器中都完成初始化就可以.
  • 如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)
struct Size {
    var width = 0.0  
    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)
    {
        self.origin = origin
        self.size = size
    }
    
    init(center:Point,size: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)
    }
}

自定义构造器

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

class SomeOne {
    var a: Int
    var b: String?
    
    // 带参数的内部名称和外部名称的构造器
    init(a aa: Int, b bb: String?) {
        a = aa
        b = bb
    }
    
    // 带参数的外部名称的构造器
    init(aa: Int, bb: String?) {
        a = aa
        b = bb
    }
    
    // 不带参数的外部名称的构造器
    init(_ a: Int, _ b: String?) {
        a = a
        b = b
    }
    
    // 带默认参数值的构造器
    // 注意:虽然这个构造器的每个参数都带有默认值可以省略参数,但是SomeOne() 调用的不是这个构造器
    init(aaa: Int = 1, bbb: String? = nil {
        a = aaa
        b = bbb
    }
}

Extension

扩展可以向已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。

扩展能向类中添加新的便利构造器,但是它们不能向类中添加新的指定构造器或析构函数。指定构造器和析构函数必须总是由原始的类实现来提供。

extension Rect {
// 上面的构造器代理调用方式可以转移到extension中
    init(center:Point,size: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)
    }
}


extension SomeOne {
    // 引用类型只能添加便利构造器
    convenience init(absA: Int) {
        self.init()
        a = abs(absA)
    }
}

类的构造过程

  • 类里面的所有存储型属性,包括所有继承自父类的属性,都必须在构造过程中设置初始值
  • Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器便利构造器

指定构造器(designated initializer)

指定构造器是类中最主要的构造器。每个类至少有一个指定构造器,一个指定构造器必须真正完成所有存储属性的初始化,并根据父类链(如果有)往上调用父类的构造器来实现父类的初始化

init(parameters) {
     statements
}

便利构造器(convenience initializer)

便利构造器是类中比较次要的。你可以定义便利构造器来调用同一个类中的其它构造器,并为其参数提供默认值,但是构造链必须到一个指定构造器为结束

convenience init(parameters) {
    self.init(parameters)
     statements
}

类的构造器代理规则

1.每个类至少要有一个指定构造器

2.指定构造器必须继承父类的指定构造器

3.便利构造器必须调用同一个类中的一个其它构造器

4.便利构造器必须以调用一个指定构造器为结束

image.png

两段式构造过程

Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性。

两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。

Swift 编译器将执行4种有效的安全检查,以确保两段式构造过程不出错地完成:

  1. 指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
  2. 指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
  3. 便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。
  4. 构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 self 作为一个值。

以下是基于上述安全检查的两段式构造过程展示:

阶段 1
  • 类的某个指定构造器或便利构造器被调用。
  • 完成类的新实例内存的分配,但此时内存还没有被初始化。
  • 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
  • 指定构造器切换到父类的构造器,对其存储属性完成相同的任务。
  • 这个过程沿着类的继承链一直往上执行,直到到达继承链的最顶部。
  • 当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
阶段 2
  • 从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问 self、修改它的属性并调用实例方法等等。
  • 最终,继承链中任意的便利构造器有机会自定义实例和使用 self
class Super {

    var a: Int = 1
    var b: Int
    init() {
        // 第一阶段
        // 构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
        b = 2
    }
}


class Sub: Super {

    var c: String
    override init() {
        // 第一阶段
        // 需要保证在向上调用之前本类所有的属性都必须先初始化完成,因此不能写在super.init()之后
        c = "3"
        super.init()
        // 第二阶段
        // 此时可以引用self,并修改它的属性
        self.a = 11
        self.b = 22
        self.c = "33"
    }
}

构造器的继承和重写

类默认是不继承父类的构造器,除了:

  • 子类中没有定义指定构造器,则子类继承父类的全部指定构造器。

  • 子类提供了父类的所有指定构造器的实现,则子类自动继承父类的所有便利构造器。

class Super {

    init() {
        //...
    }

    init(total: Int) {
        //...
    }

    convenience init(a: Int, b: Int) {
        self.init(total: a + b)
        //...
    }
}

class Sub: Super {
    // 没有定义指定构造器
}

screenshot-20220907-154849.png

class Sub: Super {
    // 未提供了父类的所有指定构造器的实现
    
    override init() {
        super.init()
        //...
    }
}

screenshot-20220907-155651.png

class Sub: Super {
    // 提供了父类的所有指定构造器的实现
    
    override init() {
        super.init()
        //...
    }
    override init(total: Int) {
        super.init(total: total)
        //...
    }
}

screenshot-20220907-154849.png