构造过程与析构过程

构造过程

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

存储属性的初始赋值

类和结构体在在创建实例时,必须为所有存储属性设置合适的初始值。存储属性的值不能处于一个位置的状态。你可以在构造器中为存储属性赋初始值,也可以再定义属性是设置其默认值。

注意
当你为存储属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察器

构造器

构造器在创建某个特定类型的新实例时被调用。以关键字init命名

1
2
3
init(){
// 在此执行构造过程
}
默认属性值

你可以在构造器中为存储属性设置初始值,也可以在属性声明时设置器默认值。提倡后者

构造参数

构造参数的语法跟函数和方法的参数相同

1
2
3
4
5
6
7
8
9
10
11
struct Celsius {
var temperatureInCelsius : Double
init(fromFahrenheit fahrenheit: Double){
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double){
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Clesius(fromFahrenheit: 200)
let freezingPointOfWater = Clesius(fromKelvin: 273)
参数名和参数标签

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

如果定义构造器没有提供参数标签,Swift会为构造的每一个参数自动生成一个参数标签。

不带参数标签的构造器

如果你不希望构造器的某个参数提供参数标签,你可以使用’ _ ‘显示的来描述他的外部名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
}
}
let magenta = Color(red: 1.0, green: 1.0 blue: 1.0)
let white = Color(2.0)
可选属性类型

如果你定制的类包含一个逻辑上允许取值为空的存储属性。

构造过程中常量属性的赋值

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

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

默认构造器

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

1
2
3
4
5
6
class ShoppingListItem {
var name: String ?
var quantity = 1
var purchased = false
}
var itme = ShopingListItem()

结构体的逐一成员构造器

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

1
2
3
4
struct Size {
var width = 0.0 , height = 0.0
}
let size = Size(width: 5.0, height: 2.0)

值类型的构造器代理

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

对于值类型,你可以使用self.init在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 self.init

注意

  • 如果你为某个值类型定义了自定义构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问成员逐一构造器)。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Size {
var width = 0.0, height = 0.0
}
struct Point{
var x = 0.0, 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(x: originX, y: originY, size: size))
}
}

类的继承和构造过程

类里面的所有存储属性(包括所有继承自父类的属性)都必须在构造过程中设置初始值。Swift为类提供了指定构造器和便利构造器来确保实例中所有存储型属性都能获得初始值

1
2
3
4
5
6
7
8
//指定构造器语法 跟值类型的简单构造器一样
init(parameters){
statements
}
//便利构造器语法
convenience init(parameters){
statements
}
类的构造器的代理规则
  1. 指定构造器必须调用其直接父类的指定构造器
  2. 便利构造器必须调用同类中定义的其他构造器
  3. 便利构造器最后必须调用指定构造器
两断式构造过程

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

构造器的继承和重写

与OC中的子类不同,Swift中的子类默认情况不会继承父类构造器。

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

假如你希望自定义子类能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。

当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是重写父类的这个指定构造器。因此,你必须在子类构造时带上override修饰符

相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器。因此,你的子类并未对一个父类构造器提供重写。不需要加 override修饰符

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

构造器的自动继承

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

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

  1. 如果子类没有定义任何指定构造器,它将自动继承父类中的所有指定构造器
  2. 如果子类提供了所有父类指定构造器的实现–无论是通过规则1继承过来的还是提供了自定义实现。它将自动继承所有便利构造器

注意对于规则2,子类可以将父类的指定构造器实现为便利构造器

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Food {
var name : String
init(name: String){
self.name = name
}
convenience init(){
//便利构造器一定要调用指定构造器
self.init(name: "[Unnamed]")
}
}
let nameMeat = Food(name: "Bacon")
let mysteryMeat = Food()

class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int){
self. quantity = quantity
super.init(name: name)
}
//这里重写了父类的指定构造器 要用 override 修饰
//实现了父类所有指定构造器,将自动继承父类的便利构造器
override convenience init(name: Stirng){
self.init(name: name, quantity: 1)
}
}

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)


class ShopingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) * \(name)"
output += purchased ? "1" : "2"
return output
}
}
//由于自己引入的属性提供了默认值,并且自己也没有指定构造器,因此ShopingListItem将自动继承父类所有指定构造器和便利构造器

var breakfastList = [ShopingListItem(),ShoppingListItem(name: "Bacon"),ShoppingListItem(name: "Eggs", quantity: 6)]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = ture
for item in breakfastList {
print(item.description)
}

可失败构造器

你可以在一个类、结构体或者枚举类型定义中,添加一个或多个可失败构造器。其语法为在 init关键字后面添加问号(init?

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

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

1
2
3
4
5
6
7
8
9
struct Animal {
let species: String
init?(species: String){
if species.isEmpty {
return nil
}
self.species = species
}
}
枚举类型的可失败构造器

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character){
switch symbol{
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
带原始值的枚举类型的可失败构造器

带原始值的枚举类型会自动带一个可失败构造器 init?(rawValue:),该失败构造器有一个名为rawValue的参数,其类型和枚举类型的原始值类型一致。

1
2
3
4
enum TemperatureUnit: Character{
case kevlin = "K", Celsius = "C", Fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
构造失败的传递

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

无论是向上代理还是横向代理,如果你代理到其他可失败构造器触发构造失败,整个构造器就立即终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Product {
let name: String
init?(name: String){
if name.isEmpty { return nil }
self.name = name
}
}
calss CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int){
if auantity < 1 { return nil }
self.quantity = quantity
super.init(name: name)
}
}
重写一个可失败构造器

你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Document {
var name : String ?
init {}
init?(name: String){
self.name = name
if name.isEmpty {
return nil
}
}
}
class AutomaticallyNameDocument: Document {
override init(){
super.init()
self.name = "[Untitled]"
}
override init(name: String){
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}

class UntitledDocument : Document {
override init() {
//强行解包
super.init(name: "[Untitled]")!
}
}
init!可失败构造器

init!将会建立一个对应类型的隐式解包可选类型对象

必要构造器

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

1
2
3
4
5
class SomeClass {
required init(){
//构造器的实现代码
}
}

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

1
2
3
4
5
class SomeSubclass : SomeClass {
required init () {
// 构造器的实现
}
}

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

如果存储属性的默认值需要一些定制或设置,你可以使用闭包全局函数为其提供定制的默认值

1
2
3
4
5
6
class SomeClass {
let someProperty: SomeType = {
//someValue 必须和SomeType类型相同
return someValue
}()
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Chessboard {
let boardColors: [Bool] = {
var temporaryBoard = [Bool]()
var isBlack = false
for i in 1...8 {
for j in 1..8{
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squarIsBlackAt(raw: Int, column: Int) ->Bool {
return boardColors[(raw * 8) + column]
}
}

析构过程

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

析构过程原理

Swift 会自动释放不再需要的实例以释放资源

1
2
3
deinit {
//执行析构过程
}

析构器在实例释放前会被自动调用。你不能主动调用析构器。

直到实例析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性。

析构器实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Bank {
static var coinsInBank = 10000;
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInbank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func recive(coins: Int){
coinsInBank += coins
}
}
class Player : Int {
var coinsInpurse: Int
init(coins: Int){
coinsInpurse = Bank.distribute(coins: coins)
}
func win(cons: Int){
coinsInpurse += Bank.distribute(coins: coins)
}
deinit{
Bank.recive(coins: coinsInpurse)
}
}
0%