Swift-构造过程

  • 过定义构造器来实现构造过程,它就像用来创建特定类型新实例的特殊方法。
  • Swift 的构造器没有返回值。它们的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。

1. 存储属性的初始赋值

  • 类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。

  • 存储型属性的值不能处于一个未知的状态。

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

1.1 构造器

  • 构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何形参的实例方法,以关键字 init 命名:
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,并在里面将存储型属性 temperature 的值初始化为 32.0(华氏温度下水的冰点)。

1.2 默认属性值

  • 可以在属性声明时为其设置默认值。
  • 可以通过在属性声明时为 temperature 提供默认值来使用更简单的方式定义结构体 Fahrenheit :
struct Fahrenheit {
    var temperature = 32.0
}
注意
    1. 如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。
    2. 两种方法的最终结果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。它能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性,后续章节将讲到。

2. 自定义构造过程

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

2.1 形参的构造过程

  • 可以在定义中提供构造形参,指定其值的类型和名字。构造形参的功能和语法跟函数和方法的形参相同。
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

第一个构造器拥有一个构造形参,其实参标签为 fromFahrenheit,形参命名为 fahrenheit;
第二个构造器也拥有一个构造形参,其实参标签为 fromKelvin,形参命名为 kelvin。
这两个构造器都将单一的实参转换成摄氏温度值,并保存在属性 temperatureInCelsius 中。

2.2 形参命名和实参标签

  • 构造形参可以同时使用在构造器里使用的形参命名和一个外部调用构造器时使用的实参标签。
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)    // 报编译期错误-需要实参标签
注意,如果不通过实参标签传值,这个构造器是没法调用的。如果构造器定义了某个实参标签,就必须使用它,忽略它将导致编译期错误。

2.3 不带实参标签的构造器形参

  • 如果你不希望构造器的某个形参使用实参标签,可以使用下划线(_)来代替显式的实参标签来重写默认行为。
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) 意图明确,不需要实参标签。因此适合使用 init(_ celsius: Double) 这样的构造器,从而可以通过提供未命名的 Double 值来调用构造器。

2.4 可选属性类型

  • 如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它声明为 可选类型。
  • 可选类型的属性将自动初始化为 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? 类型,或者说是 “可选类型 String“。
当 SurveyQuestion 的实例初始化时,它将自动赋值为 nil,表明“暂时还没有字符“。

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

  • 可以在构造过程中的任意时间点给常量属性赋值,只要在构造过程结束时它设置成确定的值。
  • 一旦常量属性被赋值,它将永远不可更改。
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.)"
注意
    对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;
    不能在子类中修改。

3. 默认构造器

  • 如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器。
  • 这个默认构造器将简单地创建一个所有属性值都设置为它们默认值的实例。
class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

上面例子中使用默认构造器创造了一个 ShoppingListItem 类的实例(使用 ShoppingListItem() 形式的构造器语法),并将其赋值给变量 item。(由于 name 属性是可选 String 类型,它将接收一个默认 nil 的默认值,尽管代码中没有写出这个值)

3.1 结构体的逐一成员构造器

  • 结构体如果没有定义任何自定义构造器,它们将自动获得一个逐一成员构造器(memberwise initializer)。
  • 不像默认构造器,即使存储型属性没有默认值,结构体也能会获得逐一成员构造器。
struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)    // 打印 "0.0 2.0"

let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)  // 打印 "0.0 0.0"

4. 值类型的构造器代理

  • 通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理。
  • 它能避免多个构造器间的代码重复。
  • 值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。
  • 类则不同,它可以继承自其它类。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
  • 对于值类型,你可以使用 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)
    }
}

let basicRect = Rect()  // basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0)
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))    // originRect 的 origin 是 (2.0, 2.0),size 是 (5.0, 5.0)
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))    // centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0)
  1. 第一个 Rect 构造器 init(),在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是函数体是空的,使用一对大括号 {} 来表示。调用这个构造器将返回一个 Rect 实例,它的 origin 和 size 属性都使用定义时的默认值 Point(x: 0.0, y: 0.0) 和 Size(width: 0.0, height: 0.0)。
  2. 第二个 Rect 构造器 init(origin:size:),在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的。这个构造器只是简单地将 origin 和 size 的实参值赋给对应的存储型属性。
  3. 第三个 Rect 构造器 init(center:size:) 稍微复杂一点。它先通过 center 和 size 的值计算出 origin 的坐标,然后再调用(或者说代理给)init(origin:size:) 构造器来将新的 origin 和 size 值赋值到对应的属性中,构造器 init(center:size:) 可以直接将 origin 和 size 的新值赋值到对应的属性中。然而,构造器 init(center:size:) 通过使用提供了相关功能的现有构造器将会更加便捷(而且意图更清晰)。

5. 类的继承和构造过程

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

5.1 指定构造器和便利构造器

  • 指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并调用合适的父类构造器让构造过程沿着父类链继续往上进行。
  • 每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。
  • 便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为部分形参提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。

5.2 指定构造器和便利构造器的语法

  • 类的指定构造器的写法跟值类型简单构造器一样:
init(parameters) {
    statements
}
  • 便利构造器也采用相同样式的写法,但需要在 init 关键字之前放置 convenience 关键字,并使用空格将它们俩分开:
convenience init(parameters) {
    statements
}

5.3 类类型的构造器代理

Swift 构造器之间的代理调用遵循以下三条规则:

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

一个更方便记忆的方法是:

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

5.4 两段式构造过程

Swift 中类的构造过程包含两个阶段。

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

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

  • 安全检查 1: 指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
    如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类的属性在它往上代理之前先完成初始化。
  • 安全检查 2: 指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
  • 安全检查 3: 便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。
  • 安全检查 4: 构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 self 作为一个值。

类的实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,类的实例才是有效的,才能访问属性和调用方法。

阶段 1

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

阶段 2

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

5.5 构造器的继承和重写

  • Swift 中的子类默认情况下不会继承父类的构造器。

  • Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,而在用来创建子类时的新实例时没有完全或错误被初始化。

      注意:父类的构造器仅会在安全和适当的某些情况下被继承。、
    
class Vehicle { //  Vehicle 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

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

class Bicycle: Vehicle {    //  Bicycle 定义了一个自定义指定构造器 init()。这个指定构造器和父类的指定构造器相匹配,所以 Bicycle 中这个版本的构造器需要带上 override 修饰符。
    override init() {
        super.init()    //  这个方法的作用是调用 Bicycle 的父类 Vehicle 的默认构造器,可以确保 Bicycle 在修改属性之前,它所继承的属性 numberOfWheels 能被 Vehicle 类初始化。
        numberOfWheels = 2
    }
}

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")    // 打印“Bicycle: 2 wheel(s)”

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() 在这里被隐式调用
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}

let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver

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

5.6 构造器的自动继承

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

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

class Food {    //  一个简单的用来封装食物名字的类
    var name: String
    init(name: String) {
        self.name = name
    }

//  RecipeIngredient 用来表示食谱中的一项原料
    convenience init() {    //  没有参数的便利构造器 init(),为新食物提供了一个默认的占位名字,通过横向代理到指定构造器 init(name: String) 并给参数 name 赋值为 [Unnamed] 来实现
        self.init(name: "[Unnamed]")
    }
}

let namedMeat = Food(name: "Bacon")     //  这个构造器可以使用一个特定的名字来创建新的 Food 实例
// 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)

//  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 ✘

6. 可失败构造器

  • 有时,定义一个构造器可失败的类,结构体或者枚举是很有用的。

  • 这里所指的“失败” 指的是,如给构造器传入无效的形参,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。

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

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

      注意:
          1. 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。
          2. 严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用 return nil 表明可失败构造器构造失败,而不要用关键字 return 来表明构造成功。
    
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”

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”

实现针对数字类型转换的可失败构造器。确保数字类型之间的转换能保持精确的值,使用这个 init(exactly:) 构造器。如果类型转换不能保持值不变,则这个构造器构造失败。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty {    //  这个可失败构造器检查传入的species 值是否为一个空字符串。
            return nil  //  如果为空字符串,则构造失败。
        }
        self.species = species  //  否则,species 属性被赋值,构造成功。
    }
}
  • 可以通过该可失败构造器来尝试构建一个 Animal 的实例,并检查构造过程是否成功:
let someCreature = Animal(species: "Giraffe")   // someCreature 的类型是 Animal? 而不是 Animal

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}   // 打印“An animal was initialized with a species of Giraffe”
  • 如果你给该可失败构造器传入一个空字符串到形参 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”
注意:
    1. 检查空字符串的值(如 "",而不是 "Giraffe" )和检查值为 nil 的可选类型的字符串是两个完全不同的概念。
    2. 上例中的空字符串("")其实是一个有效的,非可选类型的字符串。
    3. 这里我们之所以让 Animal 的可失败构造器构造失败,只是因为对于 Animal 这个类的 species 属性来说,它更适合有一个具体的值,而不是空字符串。

6.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("This is a defined temperature unit, so initialization succeeded.")
}   // 打印“This is a defined temperature unit, so initialization succeeded.”

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

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

  • 带原始值的枚举类型会自带一个可失败构造器 init?(rawValue:)。
  • 该可失败构造器有一个合适的原始值类型的 rawValue 形参,选择找到的相匹配的枚举成员,找不到则构造失败。
enum TemperatureUnit: Character {
    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}   // 打印“This is a defined temperature unit, so initialization succeeded.”

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

6.3 构造失败的传递

  • 类、结构体、枚举的可失败构造器可以横向代理到它们自己其他的可失败构造器。

  • 类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。

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

      注意:可失败构造器也可以代理到其它的不可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。
    
class Product { 
    let name: String
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

class CartItem: Product {   //  这个类建立了一个在线购物车中的物品的模型
    let quantity: Int   //  常量存储型属性,并确保该属性的值至少为 1
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }  //  CartItem 可失败构造器首先验证接收的 quantity 值是否大于等于 1, 倘若 quantity 值无效,则立即终止整个构造过程,返回失败结果,且不再执行余下代码。
        self.quantity = quantity
        super.init(name: name)
    }
}

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

//  以一个值为 0 的 quantity 来创建一个 CartItem 实例,那么将导致 CartItem 构造器失败
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 来创建一个 CartItem 实例,那么将导致父类 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”

6.4 重写一个可失败构造器

  • 可以在子类中重写父类的可失败构造器。

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

  • 这使你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。

      注意:
          1. 当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。
          2. 你可以用非可失败构造器重写可失败构造器,但反过来却不行。
    
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) {   //  用一个不可失败构造器 init(name:) 重写了父类的可失败构造器 init?(name:),子类用一个不可失败构造器代替了父类的可失败构造器。
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

class UntitledDocument: Document {
    override init() {   //  name 属性的值总是 "[Untitled]",它在构造过程中使用了父类的可失败构造器 init?(name:)
        super.init(name: "[Untitled]")!
    }
  1. 如果在调用父类的可失败构造器 init?(name:) 时传入的是空字符串,那么强制解包操作会引发运行时错误。
  2. 不过,因为这里是通过字符串常量来调用它,构造器不会失败,所以并不会发生运行时错误。

6.5 init! 可失败构造器

  • init 关键字后添加问号的方式(init?)来定义一个可失败构造器。
  • init 后面添加感叹号的方式来定义一个可失败构造器(init!),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。
  • 可以在 init? 中代理到 init!,反之亦然。
  • 你也可以用 init? 重写 init!,反之亦然。
  • 你还可以用 init 代理到 init!,不过,一旦 init! 构造失败,则会触发一个断言。

7. 必要构造器

  • 在类的构造器前添加 required 修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
    required init() {
        // 构造器的实现代码
    }
}
  • 子类重写父类的必要构造器时必须在子类的构造器前也添加 required 修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加 override 修饰符
class SomeSubclass: SomeClass {
    required init() {
        // 构造器的实现代码
    }
}

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

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

  • 如果某个存储型属性的默认值需要一些自定义或设置,你可以使用闭包或全局函数为其提供定制的默认值。
  • 每当某个属性所在类型的新实例被构造时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
  • 这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。
  • 用闭包为属性提供默认值:
class SomeClass {
    let someProperty: SomeType = {
        // 在这个闭包中给 someProperty 创建一个默认值
        // someValue 必须和 SomeType 类型相同
        return someValue
    }()
}
  1. 注意闭包结尾的花括号后面接了一对空的小括号。
  2. 这用来告诉 Swift 立即执行此闭包。
  3. 如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
    注意
        1. 如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。
        2. 这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。
        3. 同样,你也不能使用隐式的 self 属性,或者调用任何实例方法。
struct Chessboard {
    /*
    boardColors:一个包含 64 个 Bool 值的数组
    值为 true 的元素表示一个黑格
    值为 false 的元素表示一个白格
    数组中第一个元素代表棋盘上左上角的格子
    最后一个元素代表棋盘上右下角的格子。
    */
    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]
    }
}

let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// 打印“true”
print(board.squareIsBlackAt(row: 7, column: 7))
// 打印“false”
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个...
    CDLOG阅读 336评论 0 1
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,731评论 2 9
  • 中文文档 一、存储属性的初始赋值 类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能...
    伯wen阅读 179评论 0 1
  •  构造过程是使用类、结构体或枚举类型一个实例的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个...
    EndEvent阅读 629评论 0 3
  • 本章将会介绍 存储属性的初始赋值自定义构造过程默认构造器值类型的构造器代理类的继承和构造过程可失败构造器必要构造器...
    寒桥阅读 766评论 0 0