构造过程

在类、结构体、枚举实例可用前必须执行构造过程,用来设置存储属性的初始值和其他的设置或初始化任务

通过定义构造器来实现构造过程,构造器是用来创建特定类型新实例的特殊方法。

类的实例也可以通过定义析构器在实例释放之前执行特定的清除工作

注意:

与OC不同,swift的构造器无序返回值,只需保证实例在第一次使用前完成正确的初始化

存储属性的初始化赋值

类和结构体在创建实例时,存储属性必须有合适的值
可以在构造器中为存储型属性赋值,也可以在定义属性值时设置默认值,两种方法

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

构造器

//最简单形式
init() {
    // 在此处执行构造过程
}

默认属性值

struct Fahrenheit {
    var temperature = 32.0
}

自定义构造过程

可以通过输入形参和可选属性类型来自定义构造过程,也可以在构造过程中分配常量属性

构造参数

自定义构造过程,在定义中提供构造参数

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 = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0

参数的内部名称和外部名称

构造器并没有可区分的名字,都为init,因此需要构造器中的参数名和类型来确定应该被调用的构造器,因此外部名称很重要。

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: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

//只要构造器定义了外部名称就必须使用,否则报错
let veryGreen = Color(0.0, 1.0, 0.0)//报编译时错误,需要外部名称

注意:

如果不通过实参标签传值,这个构造器是没法调用的。
如果构造器定义了实参标签,就必须使用它,忽略会导致编译器错误

不带外部名的构造器参数

使用下划线来_来不为构造器某个参数提供外部名称

struct Celsius {
    var temperatureInCelsius: Double
    init(_ celsius: Double){
        temperatureInCelsius = celsius
    }
}

let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0

此时构造器意图明确 可以不需要外部参数名称

可选属性类型

在类型中包含一个逻辑上允许为空的存储型属性,要将其定义为可选类型,可选类型属性在初始化时自动初始化为nil

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?类型,在实例初始化是,自动赋值为nil

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

在构造过程中任意时间点都可以给常量属性指定值,直到构造过程结束为一个确定的值,一旦常量属性赋值就永不能更改

注意:

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

class SurveyQuestion {
    let text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}

默认构造器

如果结构体或者类的所有属性都有默认值,那么Swift会提供一个默认构造器,将所有值设置为默认值

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

结构体的逐一成员构造器

结构体中,如果没有提供任何自定义构造器,会自动获得逐一成员构造器

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

值类型的构造器代理

构造器可以通过调用其他构造器来完成实例的部分构造过程,即构造器代理

构造器代理的实现规则和形式在值类型和类类型中有所不同。
值类型(结构体或者枚举)不支持继承,所以构造代理只能代理自己的其他构造器
类类型可以继承,需要保证其所有继承的存储型属性在构造时也能正确的初始化

值类型,可以使用也只能用self.init在构造器中内部引用同类型的其他构造器

注意:

如果为某个值类型定义一个自定义构造器,将无法访问默认构造器(如果是结构体也无法访问逐一构造器),这种限制防止在给值类型增加一个复杂的自定义构造器后仍然有人使用自动生成的构造器

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

类的继承和构造过程

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

Swift提供了两种构造器来确保所有存储属性都能获取初始值: 指定构造器 便利构造器

指定构造器和便利构造器

指定构造器初始化类中提供的所有属性,并根据父类链向上条用父类构造器实现父类的初始化
每个类都必须至少有一个指定构造器,可以通过继承父类的指定构造器来满足条件;

便利构造器是次要、辅助的构造器,定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。应该只在必要时候为类提供便利构造器。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。

指定构造器、便利构造器语法

类的指定构造器语法与值类型简单构造器相同:

init(parameters) {
    statements
}

便利构造器在init关键字前加convenience关键字

convenience init(parameters) {
    statements
}

类的构造器代理规则

构造器之间代理调用限制:

  1. 指定构造器必须调用其直接父类的指定构造器
  2. 便利构造器必须调用同类中定义的其它构造器
  3. 便利构造器必须最终导致一个指定构造器被调用

两段式构造过程

Swift中类的构造过程包含两个阶段:
第一阶段: 存储属性指定一个初始值
第二阶段: 进一步定制它们的存储型属性

可以防止属性值在初始化前被访问和被另外的构造器意外赋其他值

两段式构造过程的构造流程:

阶段1:

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

阶段2:

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

注意:

构造器在第一阶段完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值

构造器的继承和重写

OC不同,swift的子类默认情况下不继承父类构造器,以防止父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例

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

注意:

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

但是,在子类中写一个和父类相同的便利构造器,因为子类不能直接调用父类的便利构造器,因此严格意义上来说,子类并未对并未对其进行重写,因此也不需要加上override前缀

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

因为存储属性有默认值,因此会自动获得一个默认构造器

class Bicycle: Vehicle {
    override init() {
        super.init()//可以确保继承的属性 numberOfWheels 能被 Vehicle 类初始化
        numberOfWheels = 2
    }
}
//子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。

如果子类构造器没有在阶段2过程中做自定义操作,并且父类有一个无参数的指定构造器,可以再所有子类的存储属性赋值后省略super.init()调用

构造器的自动继承

默认子类不继承父类构造器,但是如果满足特定条件是可以被自动继承的

如果为子类引用的新属性都提供了默认值:

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

注意:

子类可以将父类的指定构造器实现为便利构造器来满足规则2

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

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon”
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]
class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    //便利构造器重写了父类的指定构造器
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}
let oneMysteryItem = RecipeIngredient() //会继承父类的便利构造器
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ?" : " ?"
        return output
    }
}
/**
    子类引用的属性都提供了默认值,并且没有自定义构造器,将自动继承所有父类中指定和便利
    构造器
*/

可失败构造器

如果类、结构体、枚举类型对象在构造时可能会失败,则为其定义一个可失败构造器

可失败构造器通过在init关键字后添加?

注意:

可失败构造器的参数名和参数类型不能与其他非可失败构造器参数名及参数类型相同

严格来说,构造器都不支持返回值。因为构造器本身的作用,之确保对象能正确构造。因此只是通过return nil来表示可失败构造器构造失败,因此也不用return来表示构造成功

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

let anonymousCreature = Animal(species: "")
// anonymousCreature 的类型是 Animal?, 而不是 Animal
if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"

枚举类型的可失败构造器

通过提供的参数来获取特定的枚举成员,如果参数无法匹配任何枚举成员,则构造失败

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的和枚举类型原始值类型相同,如果能和枚举成员原始值匹配则构造响应枚举成员,否则构造失败

enum TemperatureUnit: Character {
    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}

构造失败的传递

类,结构体,枚举的可失败构造器可以横向代理到类型中的其他可失败构造器,也能向上代理到父类可失败构造器
如果在代理的
其他可失败构造器触发构造失败,构造过程立即终止,接下来构造代码不执行

注意;

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

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初始化CarItem导致父类构造过程失败
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”

可失败构造器重写

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

注意:

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

不能用子类的可失败构造器重写非可失败构造器

class Document {
    var name: String?
    // 该构造器创建了一个 name 属性的值为 nil 的 document 实例
    init() {}
    // 该构造器创建了一个 name 属性的值为非空字符串的 document 实例
    init?(name: String) {
        self.name = name
        if name.isEmpty { return nil }
    }
}

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    //用非可失败构造器 init(name:) 重写了父类的可失败构造器 init?(name:)
    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后加上!定义一个可失败构造器,会构造一个对应类型的隐式解包可选类型对象

可以在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或者调用任何实例方法

闭包初始化存储属性

struct Checkerboard {
//设置100个bool颜色的数组
    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 squareIsBlackAtRow(row: Int, column: Int) -> Bool {
        return boardColors[(row * 8) + column]
    }
}

let board = Checkerboard()
print(board.squareIsBlackAtRow(0, column: 1))
// 打印 "true"