iOS开发之Swift篇(12)—— 构造和析构

版本

Xcode 11.3.1
Swift 5.1.3

构造过程

构造过程是使用类、结构体或枚举类型的实例之前的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。使用关键字 init
对应的, 类的实例也可以通过实现析构器来执行它释放之前自定义的清理工作, 称为析构过程。使用关键字 deinit

构造器

构造器有很多种类型(称呼): 指定构造器(Designated)、便利构造器(Convenience)、必要构造器(required)、默认构造器、结构体的逐一成员构造器、可失败构造器等等. 虽然名称不同, 职能不一, 但到底还是一个构造过程. 我们挑选一些经常遇到的构造器来进行讨论.

指定构造器

语法:

init(parameters) {
    
    
    statements
}

指定构造器是类中最主要的构造器。
每一个类都必须至少拥有一个指定构造器; 一个指定构造器将初始化类中提供的所有属性。
先来看个例子:

class People {
    
    
    var age: Int
    init() {
    
    
        age = 1
    }
}

let people = People()
print("age = \(people.age)")
// age = 1

这个简单的例子中, People只有一个存储属性age, 和一个指定构造器init().
在这个指定构造器 init() 中, 初始化了age这个属性使之获得一个初始值.
假如我们新增一个属性 height, 而没有在指定构造器中初始化, 那么还将会报错:

class People {
    
    
    var age: Int
    var height: Double
    init() {
    
    
        age = 1
        // 没有初始化height 报错
    }
}

上面的 init() 小括号中是不带参数的, 其实我们也可以给指定构造器设置形参:

class People {
    
    
    var age: Int
    init(newAge: Int) {
    
    
        age = newAge
    }
    // 可以有另一个构造器
    init(aAge: Int) {
    
    
        age = aAge
    }
}

let people = People(newAge: 2)
let people1 = People(aAge: 3)
print("age = \(people.age)")
// age = 2
print("age1 = \(people1.age)")
// age1 = 3

跟函数和方法形参相同, 也可以使用外部参数名(参数标签), 或者前面加"_"以省略参数名

便利构造器

语法:

convenience init(parameters) {
    
    
      statements
}

便利构造器是类中比较次要的、辅助型的构造器。
顾名思义, 便利构造器主要是用来提供便利的. 比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
便利构造器必须调用同类中的其他构造器, 而且便利构造器的最终节点必须是指定构造器. 这一点我们后面还会再介绍.
举例, 上面People例子中我们添加几个参数:

class People {
    
    
    
    var name: String
    var age: Int
    var height: Double
    var weight: Double

    // 指定构造器
    init(name: String, age: Int, height: Double, weight: Double) {
    
    
        self.name = name
        self.age = age
        self.height = height
        self.weight = weight
    }
    
    // 便利构造器
    convenience init() {
    
    
        self.init(name: "无名", age: 0, height: 0, weight:0)
    }
    
    // 另一个便利构造器
    convenience init(year: Int) {
    
    
        self.init(name: "小明", age: year-2000, height: Double((year-2000))*10.0, weight: Double((year-2000))*5.0)
    }
}

let people = People()
print("name = \(people.name), age = \(people.age)")
// name = 无名, age = 0

let people1 = People(year: 2020)
print("name = \(people1.name), age = \(people1.age), height = \(people1.height), weight = \(people1.weight)")
// name = 小明, age = 20, height = 200.0, weight = 100.0

两个便利构造器中都调用了指定构造器. 实例化people没有输入参数, 调用的是第一个便利构造器, 而people1调用的是第二个构造器, 他们之间根据传入的参数类型和数量来匹配.
people1中只传入年份就能得到人的年龄身高体重等, 达到便利构造目的. 这个例子给的不太恰当, 人的身高体重不是每年均等增长的. 主要是想说明便利构造和指定构造之间的调用关系.

默认构造器

如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器。
例如:

class People {
    
    
    var age = 1
    var height = 10.0
    var weight = 20.0
}

let people = People()
print("age = \(people.age), height = \(people.height), weight = \(people.weight)")
// age = 1, height = 10.0, weight = 20.0

People类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个将为所有属性设置默认值的并创建实例的默认构造器。使用 People() 形式的构造器语法,并将其赋值给常量people。

结构体的逐一成员构造器

结构体如果没有定义任何自定义构造器,它们将自动获得一个逐一成员构造器(memberwise initializer)。不像默认构造器,即使存储型属性没有默认值,结构体也能会获得逐一成员构造器。

struct Coord {
    
    
    var x = 0
    var y = 0
    var z = 0
}

let coord = Coord(x: 1, y: 2, z: 3)
print(coord.x, coord.y, coord.z)
// 1 2 3

let coord1 = Coord(x: 1)
print(coord1.x, coord1.y, coord1.z)
// 1 0 0

let coord2 = Coord(y: 2)
print(coord2.x, coord2.y, coord2.z)
// 0 2 0

let coord3 = Coord(z: 3)
print(coord3.x, coord3.y, coord3.z)
// 0 0 3

let coord4 = Coord(x: 1, y: 2)
print(coord4.x, coord4.y, coord4.z)
// 1 2 0

或者不赋任何初始值, 但创建实例的时候必须穷举:

struct Coord {
    
    
    var x: Int
    var y: Int
    var z: Int
}

let coord = Coord(x: 10, y: 20, z: 30)
print(coord.x, coord.y, coord.z)
// 10 20 30

注意, 只适用于结构体struct中, 而不适用于类class中

可失败的构造器

如果一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。语法为在 init 关键字后面添加问号(init?)。
可失败的原因可能有:

  • 传入无效的参数值。
  • 缺少某种所需的外部资源。
  • 没有满足特定条件。

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

值类型与类类型的可失败构造器有以下区别:

  • 值类型(结构体或枚举)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制
  • 类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。

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

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
        }
    }
}

let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    
    
    print("这是一个已定义的温度单位,所以初始化成功。")
}
// 打印 这是一个已定义的温度单位,所以初始化成功。

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    
    
    print("这不是一个已定义的温度单位,所以初始化失败。")
}
// 打印 这不是一个已定义的温度单位,所以初始化失败。

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

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

下面这个例子,定义了一个名为 CartItem 的 Product 类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为 quantity 的常量存储型属性,并确保该属性的值至少为 1:

class Product {
    
    
    let name: String
    init?(name: String) {
    
    
        if name.isEmpty {
    
     return nil }
        self.name = name
    }
}

class CartItem: Product {
    
    
    let quantity: Int
    init?(name: String, quantity: Int) {
    
    
        if quantity < 1 {
    
     return nil }
        self.quantity = quantity
        super.init(name: name)
    }
}


// 传入一个非空字符串 name 以及一个值大于等于 1 的 quantity, 构造成功
if let twoSocks = CartItem(name: "sock", quantity: 2) {
    
    
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印“Item: sock, quantity: 2”


// 传入一个值为 0 的 quantity ,构造失败
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    
    
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    
    
    print("Unable to initialize zero shirts")
}
// 打印“Unable to initialize zero shirts”


// 传入一个值为空字符串的 name,那么将导致父类 Product 的构造过程失败
if let oneUnnamed = CartItem(name: "", quantity: 1) {
    
    
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    
    
    print("Unable to initialize one unnamed product")
}
// 打印“Unable to initialize one unnamed product”

构造器代理

构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
构造器代理的实现规则和形式在值类型和类类型中有所不同:

  • 值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。
  • 类类型可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。类类型构造器代理允许子类调用父类的指定构造器。
值类型的构造器代理

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

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)
    }
}

// origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):
let basicRect = Rect()
print("Size 结构体初始值: \(basicRect.size) ")
print("Rect 结构体初始值: \(basicRect.origin) ")

// 将origin和size的参数值赋给对应的存储型属性
let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))
print("Size 结构体初始值: \(originRect.size) ")
print("Rect 结构体初始值: \(originRect.origin) ")

// 先通过center和size的值计算出origin的坐标。
// 然后再调用(或代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
print("Size 结构体初始值: \(centerRect.size) ")
print("Rect 结构体初始值: \(centerRect.origin) ")

/**
 Size 结构体初始值: Size(width: 0.0, height: 0.0)
 Rect 结构体初始值: Point(x: 0.0, y: 0.0)
 Size 结构体初始值: Size(width: 5.0, height: 5.0)
 Rect 结构体初始值: Point(x: 2.0, y: 2.0)
 Size 结构体初始值: Size(width: 3.0, height: 3.0)
 Rect 结构体初始值: Point(x: 2.5, y: 2.5) 
 */
类类型的构造器代理

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

  1. 指定构造器必须调用其直接父类的的指定构造器。
  2. 便利构造器必须调用同类中定义的其它构造器。
  3. 便利构造器最后必须调用指定构造器。

也可以总结为以下两点:

  • 指定构造器必须总是向上代理
  • 便利构造器必须总是横向代理

一图以蔽之:
类类型构造器代理.png

析构过程

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

语法:

deinit {
    
    
    // 执行析构过程
}

析构过程原理

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

实例

var counter = 0;  // 引用计数器
class BaseClass {
    
    
    init() {
    
    
        counter += 1;
    }
    deinit {
    
    
        counter -= 1;
    }
}

var show: BaseClass? = BaseClass()
print(counter)
show = nil
print(counter)

/**
 1
 0
 */

猜你喜欢

转载自blog.csdn.net/u012078168/article/details/104552776