Swift 初始化(Initialization)

初始化(Initialization)

初始化是类、结构体、枚举类型的准备过程。这个过程涉及到所有存储属性的初始化,以及类在被使用之前的其他设置和初始化。

通过定义初始化器来实现这一过程,初始化器可称之为一种为特定类型创建实例的特殊方法。不像Objective-C,Swift的初始化器没有返回值。初始化器的主要任务是保证新实例在第一次使用之前能被正确地初始化。

类的实例还可以实现解析器,这个解析器能够在实例内存被回收之前做一些清理工作。

为存储属性设置初始值

类和结构体的实例在创建之前一定要保证所有的存储属性具有初始值,存储属性不能具有不确定的值。

你可以在初始化器中为存储属性设置初始值,也可以在存储属性定义的时候就为其设定默认值。

初始化器

初始化器在创建特定类型的实例的时候被调用的。最简单的形式的初始化器就像一个没有参数的实例方法,使用了init关键字。

init(){
    //执行初始化工作
}

以下是一个简单的例子:

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit”

默认属性值

尽管可以在初始化器中初始化存储属性值,但是在属性声明的时候也能给属性指定默认值。

注:如果在使用中总是保持相同的初始值,那么最好在属性声明的时候设定初始值,这样可以让初始化和声明更为紧密,而且让初始化更为简洁和清晰,也可以从默认值中推断类型。而且默认值可以让你利用默认初始化器和初始化器的继承。

你可以如下定义以上的结果体Fahrenheit

struct Fahrenheit {
    var temperature = 32.0
} 

自定义初始化

你可以通过输入参数和可选的属性类型自定义初始化过程,或者在初始化过程中赋值给常数属性。

初始化参数

你可以在初始化器定义的时候给初始化器提供参数,初始化参数和函数、方法的参数具有相同的作用和语法。
以下是一个简单的例子,定义了一个Celsius的结构体,来存储摄氏温度。这个结构体定义了两个初始化器init(fromFahrenheit:)init(fromKelvin:),分别定义了从不同值来构造实例。

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 is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0”

参数名字和形参标签

初始化器的参数同样具有名字和标签,名字给初始化器内部使用,标签给外部使用。

因为初始化器不能拥有不同的名字,所以参数的类型和名字就非常重要了,以此来区分那个初始化器会被调用。所以,如果你没有提供标签的话,那么初始化器就会为每个参数提供一个默认的标签。

以下是一个简单的例子:

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)
// this reports a compile-time error - argument labels are required

没有标签的初始化器参数

如果不想在初始化器参数中使用标签,可以使用下划线_来隐式代替默认标签,这样的话在调用初始化器的时候就无需写标签了,如以下的例子:

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 is 37.0

可选属性类型

如果某些属性在逻辑上可以没有值,或者初始化过程中不方便赋值,可以将其定义为可选类型。可选类型的属性可以不在初始化器中初始化。

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()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese.

在初始化器中为常量赋值

你可以在初始化过程中为常量赋值,只要在实例初始化完成之前赋值都可以,一旦为常量赋值之后,就无法再修改了。

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()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)

默认初始化器

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)

值类型的初始化器委托

初始化器能够调用其他的初始化器,来实现实例的部分初始化,这样称之为初始化器委托(Initializer delegation)这样就可以避免在多个初始化器中存在重复的代码了。

初始化器委托的工作规则,以及允许的委托形式,值类型和类类型存在些不同的地方。对于值类型(结构体和枚举类型)来说,因为不支持继承,所以它们的初始化器委托的过程相对简单一些,因为他们只能委托他们自己的已经实现的初始化器。对于类来说,因为可能继承了父类的属性,所以初始化的过程需要保证所有的属性都有合适的初始值。

对于值类型,你可以在自定义的初始化器中使用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)
    }
}
 

你可以采用三种方式来构造一个Rect实例。

let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)”

let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
                      size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)”

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                      size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                      size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

类继承和初始化

类的所有存储属性,包括从父类继承的属性,在初始化过程中都必须赋值。
Swift定义了两种初始化器,确保所有的存储属性都有初始值,它们分别是指定初始化器(Designated Initializer)和便利初始化器(Convenience Initializer)。

指定初始化器和便利初始化器

指定初始化器是一个类最基本的初始化器。指定初始化器完全初始化所有的属性,以及调用父类的初始化器来完成父类链的初始化工作。

类的指定构初始化器一般都比较少,一般一个类有一个指定初始化器的情况也是非常常见的。每一个类至少有一个指定初始化器,在一些情况下,可能还需要继承父类的一个或者多个指定初始化器。

便利初始化器是次要的,你可以在便利初始化器里调用指定该类的初始化器。如果你的类中需要便利初始化器的时候你可以不定义初始化器。便利初始化器的目的是为了给常用的初始化模式提供捷径节省时间,以及让初始化过程更为清晰。

指定和便利初始化器的语法

指定初始化器的语法和值类型的简单初始化器一样:

init(parameters) {
    statements
}

对于便利初始化器则使用convenience:

convenience init(parameters) {
    statements
}

类类型的初始化委托 <span id="init_delegate">

为了简化指定初始化器和便利初始化器之间的关系,Swift提供了以下的规则:

  • 规则1 指定初始化器必须调用直系父类的指定初始化器。
  • 规则2 便利初始化器必须调用该类的其他初始化器。
  • 规则3 便利初始化器最终需要调用指定初始化器。

一个简单的口诀就是

  • 指定初始化器必须向上委托
  • 便利初始化器必须平行委托

如下图所示:

图中的父类有一个指定初始化器和两个便利初始化器。便利初始化器调用便利初始化器,最终调用指定初始化器,所以满足了规则2、3。因为父类没有父类,所以规则1不用满足。子类中有两个指定初始化器,一个便利初始化器,其中便利初始化器调用了指定初始化器其,满足规则2、3。指定初始化器都调用了父类的指定初始化器,所以满足规则1。
以下这张图则显示了更为复杂的调用关系:

两阶段初始化

Swift中类的初始化分为两个阶段。第一个阶段,为每一个存储属性分配初始值。一旦每个存储属性的初始状态确定之后,第二阶段开始。在新的实例被使用之前,每个类都有机会来进一步地自定义它的存储属性。

初始化过程使用两个阶段可以让初始化安全,两阶段初始化可以防止属性在初始化之前被访问,而且可以防止属性被其他的初始化器意外地修改。

Swift的编译器会执行四个有用的安全检查,确保两阶段的初始化无错误。

  • 安全检查1<span id = "safe_check_1"></span>
    指定初始化器必须确保在向上委托父类初始化器之前,自己的所有存储属性都被初始化。
    一旦一个对象的所有存储属性的初始状态确定下来之后,这个对象的内存就被认为是完全初始化了。所以,指定初始化器必须确保在处理父类链之前,它的所有存储属性是被初始化的。

  • 安全检查2
    从父类继承过来的属性,必须在委托父类初始化器之后再为其赋值。否则的话,为这些属性赋值之后,还是会被父类的初始化器所修改。

  • 安全检查3
    便利初始化器为任何属性赋值之前,必须调用其他初始化器。否则的话,为这些属性赋值之后,还是会被其他初始化器所修改。

  • 安全检查4
    在初始化过程第一阶段完成之前,初始化器不能调用任何实例方法,不能读取任何属性的值,以及不能引用self 类的实例在第一阶段完成之前并非完全有效的。只有当第一阶段完成之后,属性才能被访问,方法才能被调用,类的实例才是有效的。

以下是基于四个安全检查的两阶段初始化过程:
阶段1

  • 一个指定初始化器或者便利初始化器被调用。
  • 为这个新实例申请内存,但是内存还没被初始化。
  • 指定初始化器确保这个类的所有的存储属性都有值,那么这块存储属性的内存就被初始化了。
  • 指定初始化器切换到父类的初始化器,执行相同的过程,确保父类的所有的存储属性都有值。
  • 沿着继承链往上,切换成父类的初始化器,执行初始化过程,直至链的顶端。
  • 当到达继承链顶端,而且最后一个父类的所有存储属性都有之后,那么这个实例的内存可以认为是完全初始化了。那么第一阶段就完成了。

阶段2

  • 从继承链下来,每一个指定指定初始化器都有其他的选项来进一步定制实例,这时候初始化器就可以访问self,修改属性和调用实例方法等等。
  • 最后,继承链上的所有的便利初始化器可以有选择的定制实例和使用self来工作。

假设有一个子类继承父类,其第一阶段看起来如下图。


在这个例子中,子类的便利初始化器被调用,这个初始化器目前不能修改任何的的属性,它只能横向委托指定初始化器。指定初始化器保证子类所有的存储属性都有值,正如安全检查1中所述,子类完成自己的存储属性初始化之后,调用父类的指定初始化器完成父类存储属性的初始化。父类确保自己所有的存储属性都有值,因为没有父类了,所以不用再委托初始化了。只要父类确保自己所有的存储属性都有值之后,它的内存可以认为是完全初始化了,第一阶段就完成了。

第二阶段看起来如下面的图所示。

父类的指定初始化器现在有机会来进一步定制实例了(虽然这个是非必须的)。一旦父类的指定初始化器完成之后,子类的指定初始化器就可以执行额外的定制了(虽然这个也是非必须的)。一旦子类的指定初始化器完成之后,那么最开始调用的便利初始化器就可以执行额外的定制了。

初始化器的继承和重写

不像Objective-CSwift的子类并没有默认继承他们父类的初始化器。Swift的这种做法可以防止一种情况,就是一个更为特别的子类继承了父类的一个简单的初始化器,并且利用它来创建一个新的子类实例,创建的实例并没有完成或者正确的初始化。

如果你想要子类保留和父类的相同初始化器的话,你只能在子类中自定义来实现这些初始化器了。

如果你写的子类的初始化器和父类相同的话,那么你最好重写父类的初始化器,用关键字override。甚至你在重写自动生成的默认初始化器时,也是需要添加override的。

正如重写的属性、方法和下标一样,override关键字会让Swift去检查父类是否有匹配的初始化器,验证参数是否满足要求。

当你重写父类的指定初始化器时,你一般需要写override关键字的,尽管你在子类实现的是便利初始化器。

相反,如果子类的初始化器和父类的便利初始化器相匹配的话,而正如类初始化委托那里所讲,子类的初始化器无法直接调用父类的便利初始化器,所以这种情况下无法重写父类的初始化器。所以说,当你子类的初始化器和父类的便利初始化器相匹配的时候,就能再使用override关键字了,因为并没有重写父类的初始化器。

以下是一个例子:

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

这里的父类Vehicle有两个属性,一个是存储属性,一个是计算属性。而且存储属性有默认值,所以这个类有一个默认的初始化器,可以利用这个默认的初始化器来构造一个实例。

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

以下定义一个子类:

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

子类Bicycle定义了一个自定义的初始化器init,因为和父类的默认初始化器相匹配,所以需要用关键override重写。在初始化器中,因为子类没有自己的存储属性,所以先调用了父类的初始化器确保父类的属性都能被初始化,然后执行额外的操作定制自己的实例,让继承过来的属性的值改为2

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

注:子类可以修改从父类继承的存储属性,无法修改从父类继承的常量属性。

初始化器自动继承

以上说到,子类在默认情况下是不会继承父类的初始化器的。但是,在某些特定情况下,是可以自动继承的。假设你已经为子类引入的存储属性赋值了,那么有以下两个规则:

  • 规则1
    如果子类没有自定义任何指定初始化器,那么子类将自动继承父类所有的指定初始化器。

  • 规则2
    如果子类实现了父类的所有的指定初始化器,不管是由规则1自动继承的,还是自定义实现的,那么子类将自动继承父类所有的便利初始化器。
    即使子类添加了便利初始化器,这两个规则也适用。

指定初始化器和便利初始化器示例

以下是一个关于指定初始化器、便利初始化器和初始化器自动继承的例子。

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

Food是基类,含有一个存储属性name,另外有一个指定初始化器init(name:String)和一个便利初始化器init(),其中便利初始化器调用了指定初始化器。

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

RecipeIngredientFood的子类,它有自己的存储属性quantity,以及从父类继承继承过来的存储属性name,定义了两个初始化器创建RecipeIngredient实例。

RecipeIngredient中定义了一个指定初始化器init(name: String, quantity: Int),这个初始化器首先初始化自己的存储属性quantity,然后向上委托父类初始化器来初始化父类的存储属性,这也满足了安全检查1
RecipeIngredient还定义了一个便利初始化器init(name: String),这个初始化器横向调用了指定初始化器,可以使得创建实例更加便利和简洁。另外,这个便利初始化器和父类的一个初始化器匹配,也就说这个便利初始化器需要override来重写父类的初始化器。因为子类已经实现了父类的所有指定初始化器,所以自动继承了父类的所有便利初始化器。
所以有三种方法创建实例

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

因为这个类没有实现任何的指定初始化器,而且这个类所引入的所有存储属性都有默认值,所以自动继承了父类的所有的指定初始化器和便利初始化器。

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

可失败的初始化器

在某些情况下,一个类、结构体或者枚举类型的的初始化过程可能失败,造成失败的原因可能是非法参数值、缺少外部资源或者其他因素。所以可失败的初始化器在这些情况下还是挺有用的。可失败初始化器意味着初始化过程可以失败,但是返回一个nil值,所以在声明初始化器的时候需要加一个问号?

注:你不能将一个初始化器同时声明为可失败的和不可失败的,即这两个初始化器具有相同的参数类型和名称。

如下的例子:

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

let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal
 
if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe

注意,someCreature是一个可选类型的值。因为初始化过程可能失败而返回一个nil。如果你传入一个空字符串的话,则返回一个nil值。

let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal
 
if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"

枚举类型的可失败初始化器

在构造枚举类型实例的时候,可能根据输入的一个或者多个参数来选择合适的值。但是如果输入的参数和已有的值不匹配的话,则可以返回一个nil。如以下的例子。

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.")
}
// Prints "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.")
}
// Prints "This is not a defined temperature unit, so initialization failed.

具有原始值的枚举类型的可失败初始化器

具有原始值的枚举类型本身就具有可失败的初始化器init?(rawValue:),如果输入的原始值匹配则输出值,如果输入的原始值不匹配的话就输出nil

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.")
}
// Prints "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.")
}
// Prints "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 }
        self.quantity = quantity
        super.init(name: name)
    }
}

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

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product

对于CartItem这个类来说,有两个地方会导致初始化过程失败。一是实例化时name的值为空串,二是实例化时quantity的值为0。

可失败初始化器的重写

你可以像其他初始化器一样重写可失败初始化器。值得注意的是,你可以将父类的可失败初始化器重写为子类的不可失败初始化器。尽管父类的初始化器是可失败的,但是如果要重写成不可失败的初始化器的话,需要处理好失败的情况。

class Document {
    var name: String?
    // this initializer creates a document with a nil name value
    init() {}
    // this initializer creates a document with a nonempty name value
    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
        }
    }
} 

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}

注:如果对父类强制解包的话,可能会导致运行时错误,需要注意处理。

init!可失败初始化器

你可以定义一个可选类型的可失败初始化器,你也定义一个隐式可选类型的可失败初始化器。将?变为!即可。

必需初始化器(Required Initializer)

在定义类的初始化器的时候用关键字required修饰的时候,说明每一个子类都必须实现这个初始化器。

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}

class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

注:你在子类中实现的那个必修初始化器必须加上关键字required,但是不用加override关键字。

使用闭包和函数设置属性默认值

如果存储属性的默认值需要一些定制或者设置,可以使用闭包或者函数来实现。无论这个类型什么时候初始化,只要这个函数或者闭包被调用,那么他的返回值就是存储属性的默认值。函数或者闭包会产生一个临时变量,然后将这个临时变量赋给这个存储属性。以下是大致的骨架:

class SomeClass {
    let someProperty: SomeType = {
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
    }()
}

注意到这个闭包的末尾的花括号处有一对圆括号,也就说这个闭包会被立即执行,然后将执行结果返回给存储属性。如果没有这对圆括号的话,就是将这个闭包赋给存储属性,显然是不会被执行的。

因为类型的初始化还没完成,所以在函数或者闭包中不可以访问其他属性,尽管这些属性可能已经有默认值了,也不可以用self

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

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

推荐阅读更多精彩内容