Swift 里构造过程

本文深入探讨了Swift中的构造过程,包括存储属性初始化、构造器的使用、默认属性值、自定义构造过程、形参命名与实参标签、可选属性类型、构造器代理、类的继承与构造过程、可失败构造器以及必要构造器等,帮助开发者理解Swift实例的生命周期和初始化过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

构造过程(实体的初始化等生命周期?)

存储属性的初始赋值(struct,enumerte)

构造器

默认属性值 (在属性声明时为其设置默认值,比构造器里设置好)

自定义构造过程 (自定义init初始化方法)

形参的构造过程 (形参:函数内部调用的参数)

形参命名和实参标签(实参标签:函数外部调用使用的参数名)

不带实参标签的构造器形参 (使用 "_ " 来表示忽略实参)

可选属性类型 (只声明,不在构造器init里赋值)

构造过程中常量属性的赋值(init里可以给常量赋值,init结束后不可变更)

默认构造器 (所有属性都在初始化时设置默认值的行为?Class() )

结构体的逐一成员构造器

值类型的构造器代理 (自定义后无法访问默认构造器)

类的继承和构造过程

指定构造器和便利构造器 (每个类必须至少拥有一个指定构造器)

指定构造器(init() ) 和便利构造器 (convenience init() ) 的语法

类类型的构造器代理

三条规则:

两段式构造过程

安全检查 

构造器的继承和重写

构造器的自动继承

自动继承的2 个规则

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

可失败构造器  init?

枚举类型的可失败构造器

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

构造失败的传递

重写一个可失败构造器

init! 可失败构造器 (不可失败的更合理?)

必要构造器(构造器前添加 required 该类的子类都必须实现)

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

总结:


构造过程(实体的初始化等生命周期?)

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

存储属性的初始赋值(struct,enumerte)

类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态

可以在构造器中为存储型属性设置初始值,也可以在定义属性时分配默认值。存储型属性分配默认值或者在构造器中为设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。

构造器

构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何形参的实例方法,以关键字 init 命名:(示例 在构造器中为存储型属性设置初始值)

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”

默认属性值 (在属性声明时为其设置默认值,比构造器里设置好)

使用默认值让属性的初始化和声明结合得更紧密。它能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性

自定义构造过程 (自定义init初始化方法)

形参的构造过程 (形参:函数内部调用的参数)

自定义构造过程时,可以在定义中提供构造形参,指定其值的类型和名字。构造形参的功能和语法跟函数和方法的形参相同

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}

调用,并没有显形的去调用init
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0

let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0

形参命名和实参标签(实参标签:函数外部调用使用的参数名)

跟函数和方法形参相同,构造形参可以同时使用在构造器里使用的形参命名和一个外部调用构造器时使用的实参标签。

构造器并不像函数和方法那样在括号前有一个可辨别的方法名。因此在调用构造器时,主要通过构造器中形参命名和类型来确定应该被调用的构造器。正因如此,如果你在定义构造器时没有提供实参标签,Swift 会为构造器的每个形参自动生成一个实参标签。

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 {
 init(_ celsius: Double){
        temperatureInCelsius = celsius
    }
}


调用也是没有显性调用 init
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0

可选属性类型 (只声明,不在构造器init里赋值)

有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它声明为 可选类型。可选类型的属性将自动初始化为 nil,表示这个属性是特意在构造过程设置为空。

class SurveyQuestion {
    var text: String
    var response: String?  可选型。自动赋值为 nil,表明“暂时还没有字符“
    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."

构造过程中常量属性的赋值(init里可以给常量赋值,init结束后不可变更)

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

class SurveyQuestion {
    let text: String
    var response: String?

    init(text: String) { 构造器里可以给常量赋值,赋完后不可更改
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 打印“How about beets?”
beetsQuestion.response = "I also like beets. (But not with cheese.)"

默认构造器 (所有属性都在初始化时设置默认值的行为?Class() )

如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 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)


调用一个逐一成员构造器(memberwise initializer)时,可以省略任何一个有默认值的属性
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)

值类型的构造器代理 (自定义后无法访问默认构造器)

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

构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类

为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制避免了在一个更复杂的构造器中做了额外的重要设置,但有人不小心使用自动生成的构造器而导致错误的情况。

Tips

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

类的继承和构造过程

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

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

指定构造器和便利构造器 (每个类必须至少拥有一个指定构造器)

指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并调用合适的父类构造器让构造过程沿着父类链继续往上进行

类倾向于拥有极少的指定构造器,普遍的是一个类只拥有一个指定构造器。

每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。

便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为部分形参提供默认值。

指定构造器(init() ) 和便利构造器 (convenience init() ) 的语法

指定构造器
init(parameters) {
    statements
}

便利构造器
convenience init(parameters) {
    statements
}

类类型的构造器代理

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

三条规则:

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

2、便利构造器必须调用类中定义的其它构造器。

3、便利构造器最后必须调用指定构造器。

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

构造器代理图

两段式构造过程

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

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

安全检查 

1、指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。(不是默认行为吗?默认声明属性时会有默认值)

2、指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。

3、便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。

4、构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 self 作为一个值(可以set Self相关属性,不能get ?)

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

构造器的继承和重写

Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,而在用来创建子类时的新实例时没有完全或错误被初始化

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

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


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

只为存储型属性提供默认值,也没有提供自定义构造器。会获得一个默认构造器
子类 Bicycle 定义了一个自定义指定构造器 init()
class Bicycle: Vehicle {
    override init() { 指定构造器和父类的指定构造器相匹配要带上 override 
        super.init()
        numberOfWheels = 2
    }
}

子类二 
class Hoverboard: Vehicle {
    var color: String

这个构造器依赖隐式调用父类的构造器来完成,而不是显示调用 super.init()
    init(color: String) {
        self.color = color
        // super.init() 在这里被隐式调用
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}

子类可以在构造过程修改继承来的变量属性,但是不能修改继承来的常量属性。

构造器的自动继承

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

自动继承的2 个规则

1、如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。(类似flutter)

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

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

基类Food
class Food {
    var name: String
    init(name: String) { //接受单一参数 name 的指定构造器
        self.name = name
    }


没有参数的便利构造器 init() 为新食物提供了一个默认的占位名字
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}


let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]

第二个类是 Food 的子类 RecipeIngredient。成份 ?? 食谱??

成份??
class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity   先自己属性赋值,然后父类的构造器? 安全检查 1
        super.init(name: name)
    }


和Food中指定构造器init(name: String)相同的形参,重写了父类的指定构造器。用override 修饰
    override convenience init(name: String) {
便利构造器,调用了指定构造器 
避免了创建多个 quantity 为 1 的 RecipeIngredient 实例时的代码重复
        self.init(name: name, quantity: 1)
    }
}

三种构造器都可以用来创建新的 RecipeIngredient 实例
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

 第三个是 RecipeIngredient 的子类,叫做 ShoppingListItem。购物单

class ShoppingListItem: RecipeIngredient {

    var purchased = false  是否购买?

    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}


使用购物清单

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
    print(item.description)
}
// 1 x orange juice ✔
// 1 x bacon ✘
// 6 x eggs ✘

可失败构造器  init?

定义一个构造器可失败的类,结构体或者枚举是很有用的。这里所指的“失败” 指的是,如给构造器传入无效的形参,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。

其语法为在 init 关键字后面添加问号(init?

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

通过 return nil 语句来表明可失败构造器在何种情况下应该 “失败”。

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// 打印“12345.0 conversion to Int maintains value of 12345”

init(exactly:) Int的API构造器。如果类型转换不能保持值不变,则这个构造器构造失败。
let valueChanged = Int(exactly: pi)
// valueChanged 是 Int? 类型,不是 Int 类型

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}
// 打印“3.14159 conversion to Int does not maintain value”

 Animal 的结构体,其中有一个名为 species 的 String 类型的常量属性。

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

使用
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// 打印“This is not a defined temperature unit, so initialization failed.”

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

带原始值的枚举类型会自带一个可失败构造器 init?(rawValue:),该可失败构造器有一个合适的原始值类型的 rawValue 形参,选择找到的相匹配的枚举成员,找不到则构造失败。

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

可以用原始值类型的 Character 和进阶的 init?(rawValue:) 
let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// 打印“This is not a defined temperature unit, so initialization failed.”

构造失败的传递

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

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

物品
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 } //确保该属性的值至少为 1
        self.quantity = quantity
        super.init(name: name)
    }
}

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印“Item: sock, quantity: 2”

倘若你以一个值为 0 的 quantity 来创建一个 CartItem 实例,那么将导致 CartItem 构造器失败:

f 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”

重写一个可失败构造器

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

父类
class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) { 从写父类的构造器
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

init! 可失败构造器 (不可失败的更合理?)

可以通过在 init 后面添加感叹号的方式来定义一个可失败构造器(init!),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象

一旦 init! 构造失败,则会触发一个断言。

必要构造器(构造器前添加 required 该类的子类都必须实现)

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


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

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

使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被构造时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。

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

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

国际象棋棋盘:黑白格交替的 8 x 8 的棋盘

Chessboard 结构体定义了一个属性 boardColors,它是一个包含 64 个 Bool 值的数组。在数组中,值为 true 的元素表示一个黑格,值为 false 的元素表示一个白格。

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 squareIsBlackAt(row: Int, column: Int) -> Bool {
        return boardColors[(row * 8) + column]
    }
}

总结:

1、构造方法init关键字,调用时省略init。init为默认构造器

2、自定义构造器:init方法后添加参数

3、实参:外部调用的入参名称。形参,方法内部自己使用的名称。(_)来代替显式的实参标签

4、常量属性可以在构造过程中赋值。赋值后不可改变

5、结构体会自动获得一个逐一成员构造器。逐一成员构造器可以省略任何一个有默认值的属性

6、用extension 扩展自定义的构造器

7、类类型有指定构造器(Designated)和便利构造器(convenience)

8、便利构造器里必须调用指定构造器

9、重写父类构造器要用override

10、子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。

11、子类自动继承父类所有的便利构造器。

12、可失败构造器init?.不满足条件时返回nil.构造不成功

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

14、可失败构造器里可以调用现有的构造器,到正常状态

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

16、init! 可失败构造器。一旦 init! 构造失败,则会触发一个断言。

17、必要构造器required 修饰符子类都必须实现该构造器

18、通过闭包或函数设置属性的默认值。使用闭包或全局函数为其提供定制的默认值。

19、闭包里访问其它属性,即使这些属性有默认值。也不能使用隐式的 self 属性

学习笔记,参考:http://www.swift51.com/swift5.1/02_language_guide/14_Initialization.html

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值