Swift3.0_X_构造过程和析构过程

构造过程

  • 构造过程是使用类、结构体或枚举类型的实例之前的准备过程;
  • 通过定义构造器来实现构造过程,这些构造器可以看做是用来创建特定类型新实例的特殊方法;
  • 与OC中的构造器不同,Swift的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化;
  • 类的实例也可以通过定义析构器在实例释放之前执行特定的清除工作

存储属性的初始赋值

  • 类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值;
  • 可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值;
  • 当为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者

1、构造器

以关键字init命名:

init() {
    // 在此处执行构造过程
}

案例:

这个结构体定义了一个不带参数的构造器init,并在里面将存储型属性temperature的值初始化为32.0

// 保存华氏温度的结构体Fahrenheit
struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 打印 "The default temperature is 32.0° Fahrenheit”

2、默认属性值

如前所述,可以在构造器中为存储型属性设置初始值。同样,也可以在属性声明时为其设置默认值

注意:如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。使用默认值能让构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能充分利用默认构造器、构造器继承等特性,后续章节将讲到。

// 可以使用更简单的方式在定义结构体Fahrenheit时为属性temperature设置默认值:
struct Fahrenheit {
    var temperature = 32.0
}

自定义构造过程

可以通过输入参数和可选类型的属性来自定义构造过程,也可以在构造过程中修改常量属性。

1、构造参数:

自定义构造过程时,可以在定义中提供构造参数,指定所需值的类型和名字。

// 面例子中定义了一个包含摄氏度温度的结构体Celsius。它定义了两个不同的构造器:init(fromFahrenheit:)和init(fromKelvin:),二者分别通过接受不同温标下的温度值来创建新的实例:

struct Celsius {
    var temperatureInCelsius: Double
    // 构造参数,其外部名字为fromFahrenheit,内部名字为fahrenheit
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    // 构造参数,其外部名字为fromKelvin,内部名字为kelvin
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0

2、参数的内部名称和外部名称:

跟函数和方法参数相同,构造参数也拥有一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。

当中有多个构造器时,需要通过外部名称来识别不同的构造器:在调用构造器时,主要通过构造器中的参数名和类型来确定应该被调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,Swift 会为构造器的每个参数自动生成一个跟内部名字相同的外部名。

以下例子中定义了一个结构体Color,它包含了三个常量:red、green和blue。这些属性可以存储0.0到1.0之间的值,用来指示颜色中红、绿、蓝成分的含量。

// Color提供了一个构造器,其中包含三个Double类型的构造参数。Color也可以提供第二个构造器,它只包含名为white的Double类型的参数,它被用于给上述三个构造参数赋予同样的值。

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

两种构造器都能用于创建一个新的Color实例,你需要为构造器每个外部参数传值:

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

注意,如果不通过外部参数名字传值,你是没法调用这个构造器的。只要构造器定义了某个外部参数名,你就必须使用它,忽略它将导致编译错误:

let veryGreen = Color(0.0, 1.0, 0.0)
// 报编译时错误,需要外部名称

3、不带外部名的构造器参数:

如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(_)来显式描述它的外部名,以此重写上面所说的默认行为。

下面是之前Celsius例子的扩展跟之前相比添加了一个带有Double类型参数的构造器其外部名用_代替

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double){
        temperatureInCelsius = celsius
    }
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0
// 调用Celsius(37.0)意图明确,不需要外部参数名称。

4、可选属性类型

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

下面例子中定义了类SurveyQuestion,它包含一个可选字符串属性response:

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// 打印 "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."

// 调查问题的答案在回答前是无法确定的,因此我们将属性response声明为String?类型,或者说是可选字符串类型。当SurveyQuestion实例化时,它将自动赋值为nil,表明此字符串暂时还没有值。

5、构造过程中常量属性的修改

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

注意:对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。

默认构造器

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

案例:下面例子中创建了一个类ShoppingListItem,它封装了购物清单中的某一物品的属性:名字(name)、数量(quantity)和购买状态 purchase state:

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

由于ShoppingListItem类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器(尽管代码中没有显式为name属性设置默认值,但由于name是可选字符串类型,它将默认设置为nil)。上面例子中使用默认构造器创造了一个ShoppingListItem类的实例(使用ShoppingListItem()形式的构造器语法),并将其赋值给变量item。

2、结构体的逐一成员构造器

除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。

逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。

// 结构体Size自动获得了一个逐一成员构造器init(width:height:)。你可以用它来为Size创建新的实例:

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

值类型的构造器代理

构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。

  • 构造器代理的实现规则和形式在值类型和类类型中有所不同;
  • 值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器;
  • 类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化;
  • 对于值类型,可以使用self.init在自定义的构造器中引用相同类型中的其它构造器。并且只能在构造器内部调用self.init;
  • 如果为某个值类型定义了一个自定义的构造器,将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制可以防止为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器;

注意:假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(extension)中,而不是写在值类型的原始定义中.

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

可以通过以下三种方式为Rect创建实例

— 使用被初始化为默认值的origin和size属性来初始化;

  • 提供指定的origin和size实例来初始化;
  • 提供指定的center和size来初始化。
struct Rect {
    var origin = Point()
    var size = Size()
    // 构造器1:在功能上跟没有自定义构造器时自动获得的默认构造器是一样的
    init() {}
    // 构造器2:在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的
    // 简单地将origin和size的参数值赋给对应的存储型属性:
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    // 构造器3:它先通过center和size的值计算出origin的坐标,然后再调用(或者说代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中:
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

类的继承和构造过程

类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。

Swift为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。

1、指定构造器和便利构造器

  • 指定构造器:是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。
  • 便利构造器:是类中比较次要的、辅助型的构造器。可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。

2、指定构造器和便利构造器的语法

类的指定构造器的写法跟值类型简单构造器一样:

init(parameters) {
    statements
}

便利构造器也采用相同样式的写法,但需要在init关键字之前放置convenience关键字,并使用空格将它们俩分开:

convenience init(parameters) {
    statements
}

3、类的构造器代理规则

为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用:

  • 规则 1 指定构造器必须调用其直接父类的的指定构造器。

  • 规则 2 便利构造器必须调用同类中定义的其它构造器。

  • 规则 3 便利构造器必须最终导致一个指定构造器被调用。

一个更方便记忆的方法是:指定构造器必须总是向上代理;便利构造器必须总是横向代理

注意: 这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类定义如何实现。

下面图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系。

4、两段式构造过程:

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

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

注意 Swift 的两段式构造过程跟OC中的构造过程类似。最主要的区别在于阶段 1,OC给每一个属性赋值0或空值(比如说0或nil)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以0或nil作为合法默认值的情况。

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

  • 安全检查 1 指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。 如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。

  • 安全检查 2 指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。

  • 安全检查 3 便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。

  • 安全检查 4 构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值。 类实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。

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

阶段 1

  • 某个指定构造器或便利构造器被调用。
  • 完成新实例内存的分配,但此时内存还没有被初始化。
  • 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
  • 指定构造器将调用父类的构造器,完成父类属性的初始化。
  • 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。
  • 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。

阶段 2

  • 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
  • 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。

下图展示了在假定的子类和父类之间的构造阶段 1:

在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。

如安全检查 1 所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着构造器链一直往上完成父类的构造过程。

父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要初始化,也就无需继续向上代理。

一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,阶段 1 完成。

以下展示了相同构造过程的阶段 2: 父类中的指定构造器现在有机会进一步来定制实例(尽管这不是必须的)。

一旦父类中的指定构造器完成调用,子类中的指定构造器可以执行更多的定制操作(这也不是必须的)。

最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的定制操作。

5、构造器的继承和重写

跟OC中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。

注意:父类的构造器仅会在安全和适当的情况下被继承。

假如希望自定义的子类中能提供一个或多个跟父类相同的构造器,可以在子类中提供这些构造器的自定义实现。 当在编写一个和父类中指定构造器相匹配的子类构造器时,实际上是在重写父类的这个指定构造器。因此,必须在定义子类构造器时带上override修饰符。即使重写的是系统自动提供的默认构造器,也需要带上override修饰符

正如重写属性,方法或者是下标,override修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否正确。

注意:当重写一个父类的指定构造器时,总是需要写override修饰符,即使子类将父类的指定构造器重写为了便利构造器。

相反,如果编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文类的构造器代理规则有所描述),因此,严格意义上来讲,子类并未对一个父类构造器提供重写。最后的结果就是,在子类中“重写”一个父类便利构造器时,不需要加override前缀。

在下面的例子中定义了一个叫Vehicle的基类。基类中声明了一个存储型属性numberOfWheels,它是值为0的Int类型的存储型属性。numberOfWheels属性用于创建名为descrpiption的String类型的计算型属性:

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

Vehicle类只为存储型属性提供默认值,而不自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考默认构造器。自动获得的默认构造器总会是类中的指定构造器,它可以用于创建numberOfWheels为0的Vehicle实例:

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

注意: 子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。


6、构造器的自动继承

如上所述,子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。

假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用:

  • 规则 1 如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。

  • 规则 2 如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。【子类可以将父类的指定构造器实现为便利构造器。】

即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。

7、指定构造器和便利构造器实践

详见文档:Swift

可失败构造器

如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。

为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面添加问号(init?)。

注意: 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。

可失败构造器会创建一个类型为自身类型的可选类型的对象。你通过return nil语句来表明可失败构造器在何种情况下应该“失败”。

注意: 严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用return nil表明可失败构造器构造失败,而不要用关键字return来表明构造成功。

1、枚举类型的可失败构造器

可以通过一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的参数无法匹配任何枚举成员,则构造失败。

2、带原始值的枚举类型的可失败构造器

带原始值的枚举类型会自带一个可失败构造器init?(rawValue:),该可失败构造器有一个名为rawValue的参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和某个枚举成员的原始值匹配,则该构造器会构造相应的枚举成员,否则构造失败。

3、构造失败的传递

类,结构体,枚举的可失败构造器可以横向代理到类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。

无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。

注意: 可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。

4、重写一个可失败构造器

如同其它的构造器,你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器。这使你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。

注意,当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。

注意 你可以用非可失败构造器重写可失败构造器,但反过来却不行。

5、可失败构造器 init!

通常来说我们通过在init关键字后添加问号的方式(init?)来定义一个可失败构造器,但你也可以通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。

你可以在init?中代理到init!,反之亦然。你也可以用init?重写init!,反之亦然。你还可以用init代理到init!,不过,一旦init!构造失败,则会触发一个断言。

必要构造器

在类的构造器前添加required修饰符表明所有该类的子类都必须实现该构造器:

class SomeClass {
    required init() {
        // 构造器的实现代码
    }
}

在子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加override修饰符:

class SomeSubclass: SomeClass {
    required init() {
        // 构造器的实现代码
    }
}

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

通过闭包或函数设置属性的默认值

如果某个存储型属性的默认值需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。

这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。

下面介绍了如何用闭包为属性提供默认值:

class SomeClass {
    let someProperty: SomeType = {
        // 在这个闭包中给 someProperty 创建一个默认值
        // someValue 必须和 SomeType 类型相同
        return someValue
    }()
}

注意闭包结尾的大括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。

注意: 如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的self属性,或者调用任何实例方法。

析构过程

析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字deinit来标示,类似于构造器要用init来标示。

析构过程原理

Swift 会自动释放不再需要的实例以释放资源。如自动引用计数章节中所讲述,Swift 通过自动引用计数(ARC)处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。

在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数,如下所示:

deinit {
    // 执行析构过程
}

析构器是在实例释放发生前被自动调用。你不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。

因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。

析构器实践

这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是Bank和Player。Bank类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个Bank存在,因此Bank用类来实现,并使用类型属性和类型方法来存储和管理其当前状态。

class Bank {
    // 使用coinsInBank属性来跟踪它当前拥有的硬币数量
    static var coinsInBank = 10_000

    // 在Bank对象分发硬币之前检查是否有足够的硬币。如果硬币不足,Bank对象会返回一个比请求时小的数字(如果Bank对象中没有硬币了就返回0)。此方法返回一个整型值,表示提供的硬币的实际数量
    static func distribute(coins numberOfCoinsRequested: Int) -> Int {
        let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
        coinsInBank -= numberOfCoinsToVend
        return numberOfCoinsToVend
    }
    // 方法只是将Bank实例接收到的硬币数目加回硬币存储中
    static func receive(coins: Int) {
        coinsInBank += coins
    }
}

// Player类描述了游戏中的一个玩家。每一个玩家在任意时间都有一定数量的硬币存储在他们的钱包中。这通过玩家的coinsInPurse属性来表示:
class Player {
    var coinsInPurse: Int
    init(coins: Int) {
        coinsInPurse = Bank.distribute(coins: coins)
    }
    // Player类定义了一个win(coins:)方法,该方法从Bank对象获取一定数量的硬币,并把它们添加到玩家的钱包。
    func win(coins: Int) {
        coinsInPurse += Bank.distribute(coins: coins)
    }
    // Player类还实现了一个析构器,这个析构器在Player实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返还给Bank对象:
    deinit {
        Bank.receive(coins: coinsInPurse)
    }
}
// 每个Player实例在初始化的过程中,都从Bank对象获取指定数量的硬币。如果没有足够的硬币可用,Player实例可能会收到比指定数量少的硬币.

var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// 打印 "A new player has joined the game with 100 coins"
print("There are now \(Bank.coinsInBank) coins left in the bank")
// 打印 "There are now 9900 coins left in the bank"

// 因为playerOne是可选的,所以访问其coinsInPurse属性来打印钱包中的硬币数量时,使用感叹号(!)来解包:

playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// 输出 "PlayerOne won 2000 coins & now has 2100 coins"
print("The bank now only has \(Bank.coinsInBank) coins left")
// 输出 "The bank now only has 7900 coins left"

// 这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而Bank对象只剩余 7,900 枚硬币。

playerOne = nil
print("PlayerOne has left the game")
// 打印 "PlayerOne has left the game"
print("The bank now has \(Bank.coinsInBank) coins")
// 打印 "The bank now has 10000 coins"

// 玩家现在已经离开了游戏。这通过将可选类型的playerOne变量设置为nil来表示,意味着“没有Player实例”。当这一切发生时,playerOne变量对Player实例的引用被破坏了。没有其它属性或者变量引用Player实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。

支持一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码支持一下

打开支付宝扫一扫,即可进行扫码打赏哦