swift构造方法执行过程

分享到:

初始化是一个编译供使用的一个类、结构体或枚举实例的一个过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。

你通过定义初始化器来实施初始化,该初始化器和特别方法相似,该方法可以被调用用来创建某个具体类型的一个新实例。与Objective-C初始化不同,Swift初始化无需返回值。它们的主要任务是保证新实例在第一次使用前完成正确的初始化。

类实例也可以通过deinitializer在类实例释放之前执行特定的清除工作。更过关于deinitializers的信息,请参阅Deinitialization。

 

存储属性的初始赋值

 

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

你可以在一个初始化器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。以下章节将详细介绍这两种方法。

注:当你为存储型属性设置默认值或者在初始化器中为其赋值时,它们的值是被直接设置的,而无需调用任何属性观察者。

初始化器

 

创建一个新的特定类型的实例时,需要调用初始化器。初始化器的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。

下面的例子定义了一个新的结构体Fahrenheit来存储在华氏标度中显示的温度。Fahrenheit结构体有一个存储属性temperature,属于Double类型:

 

struct Fahrenheit {

var temperature: Double

init() {

temperature = 32.0

}

}

var f = Fahrenheit()

println("The default temperature is \(f.temperature)° Fahrenheit")

// prints "The default temperature is 32.0° Fahrenheit"

 

该结构体定义一个不带参数的初始化器init,并将存储型属性temperature的值初始化为32.0(当用华氏标度表示时的水的冰点)。

默认属性值

 

你可以从一个初始化器内设置一个存储属性的初始值,如上所示。同样,你也可以在属性声明时为其设置默认值。当属性被定义时,你通过赋予一个初始值给该属性来指定默认属性值。

注:若一个属性一直采用同一个初始值,则在一个初始化器内提供一个默认值而非设定一个值。最终结果是相同的,只不过默认值跟属性初始化过程结合的更紧密。通过采用默认值使初始化器更简洁、更清晰,且能通过默认值自动推导出属性的类型。同时,借助默认值也能充分利用默认初始化器以及初始化器继承(后续章节将讲到)等特性。

你可以使用更简单的方式在定义结构体Fahrenheit时为属性temperature设置默认值:

 

struct Fahrenheit {

var temperature = 32.0

}

 

定制初始化

 

你可以通过输入参数和可选属性类型来定制初始化过程,也可以在初始化过程中修改常量属性。这些都将在后面章节中提到。

初始化参数

 

你可以在定义初始化器时提供初始化参数,为其提供定制初始化所需值的类型和名称。初始化参数有与函数和方法参数相同的功能和语法。

以下示例中定义了一个结构体Celsius,该结构体存储用摄氏标度表示的温度。结构体Celsiu定义了两个不同的定制初始化器:

init(fromFahrenheit:)和init(fromKelvin:),二者分别通过接受不同刻度表示的温度值来创建新的实例:

 

struct Celsius {

var temperatureInCelsius: Double = 0.0

init(fromFahrenheit fahrenheit: Double) {

temperatureInCelsius = (fahrenheit - 32.0) / 1.8

}

init(fromKelvin kelvin: Double) {

temperatureInCelsius = kelvin - 273.15

}

}

boilingPointOfWater = Celsius(fromFahrenheit: 212.0)

boilingPointOfWater.temperatureInCelsius is 100.0

freezingPointOfWater = Celsius(fromKelvin: 273.15)

freezingPointOfWater.temperatureInCelsius is 0.0

 

第一个初始化器拥有一个初始化参数,其外部名称为fromFahrenheit,内部名称为fahrenheit。第二个初始化器也拥有一个初始化参数,其外部名称为fromKelvin,内部名称为kelvin。这两个初始化器都将唯一的参数值转换成摄氏温度值,并保存在属性temperatureInCelsius中。

内部参数和外部参数名称

 

跟函数和方法参数相同,初始化参数也存在一个在初始化器内部使用的参数名称和一个在调用初始化器时使用的外部参数名称。

然而,初始化器并不像函数和方法那样在括号前有一个可辨别的名称。因此,在调用初始化器时,主要通过初始化器中的参数名称和类型来对其进行确认。正因为参数如此重要,如果你在定义初始化器时未能提供参数的外部名称,Swift会为每个初始化器的参数自动生成一个跟内部名称相同的外部名称,就相当于在每个初始化参数之前加了一个hash字符。

注:若你不想为在初始化器内的参数提供一个外部名称,则可以为该参数提供一个下划线(_)作为一个明确的外部名称来重写上述默认行为。

 

下列示例中定义了一个结构体Color,该结构体包含三个常量属性red、green和blue。这些属性存储0.0到1.0的值来表明颜色中红、绿和蓝成分的含量。

Color提供了一个初始化器,其中包含三个Double类型的经命名的参数:

 

struct Color {

let red = 0.0, green = 0.0, blue = 0.0

init(red: Double, green: Double, blue: Double) {

self.red = red

self.green = green

self.blue = blue

}

}

 

当创建一个新的Color实例时,你都需要通过三种颜色的外部参数名称来传值,并调用初始化器。

 

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

 

注,如果不通过外部参数名称传值,你是没法调用这个初始化器的。只要初始化器定义了某个外部参数名称,你就必须使用它,并忽略它将导致编译错误:

 

let veryGreen = Color(0.0, 1.0, 0.0)

// this reports a compile-time error - external names are required

 

 

可选属性类型

 

如果你定制的类型包含一个逻辑上允许取值为空的存储型属性--不管是因为它无法在初始化时赋值,还是因为它可以在之后某个时间点可以赋值为空--你都需要将它定义为可选类型optional type。

可选类型的属性将自动初始化为nil,表示这个属性是故意在初始化时设置为“no value yet”的。

下列示例中定义了类SurveyQueston,它包含一个可选字符串属性response:

 

class SurveyQuestion {

var text: String

var response: String?

init(text: String) {

self.text = text

}

func ask() {

println(text)

}

cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")

cheeseQuestion.ask()

prints "Do you like cheese?"

cheeseQuestion.response = "Yes, I do like cheese."

 

调查问题在问题提出之后,我们才能得到回答。因此,我们将属性回答response声明为String?类型,或者说是“可选字符串类型”。当SurveyQuestion实例化时,它将自动赋值为nil,表明“no string yet”。

初始化过程中常量属性的修改

 

只要在初始化过程结束前常量的值能确定,你可以在初始化过程中的任意时间点修改常量属性的值。

注:对于类实例,一个常量属性仅仅在引入该属性的类的初始化过程中才能被修改。该属性不能被子类修改。

你可以修改上面的SurveyQuestion示例,用常量属性替代变量属性text,指明问题内容text在其创建之后不会再被修改。尽管text属性现在是常量,我们仍然可以在其类的初始化器中修改它的值:

 

class SurveyQuestion {

let text: String

var response: String?

init(text: String) {

self.text = text

}

func ask() {

println(text)

}

beetsQuestion = SurveyQuestion(text: "How about beets?")

beetsQuestion.ask()

prints "How about beets?"

beetsQuestion.response = "I also like beets. (But not with cheese.)"

 

 

默认初始化器

 

Swift将为所有属性已提供默认值的且自身没有定义任何初始化器的结构体或基类,提供一个默认的初始化器。这个默认初始化器将简单的创建一个所有属性值都设置为默认值的实例。

以下示例中创建了一个类ShoppingListItem,它封装了购物清单中的某一项的属性:名称、数量和购买状态:

 

class ShoppingListItem {

var name: String?

var quantity = 1

var purchased = false

}

var item = ShoppingListItem()

 

因为ShoppingystItem类所有属性有默认值,且因为该类是基类且没有超类,它将自动获得一个可以为所有属性设置默认值的默认初始化器(尽管代码中没有显式为name属性设置默认值,但由于name是可选字符串类型,它将默认设置为nil)。上述示例中使用默认初始化器创造了一个ShoppingListItem类的实例(使用ShoppingListItemo形式的初始化器语法),并将其赋值给变量item。

结构体类型的逐一成员初始化器

 

除了上述提及的默认初始化器之外,若结构体类型对其存储属性提供了默认值且自身没有提供定制的初始化器,它们能自动获得一个逐一成员初始化器。

逐一成员初始化器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员初始化器时,通过与成员属性名称相同的参数名称进行传值来完成对成员属性的初始赋值。

以下示例中定义了一个结构体Size,它包含两个属性width和height。Swift可以根据这两个属性的初始赋值0.0自动推导出它们的类型Double。

由于这两个存储型属性都有默认值,结构体Size自动获得了一个逐一成员初始化器init(width:height:)。你可以调用其对新建的Size实体进行初始化:

 

struct Size {

var width = 0.0, height = 0.0

}

let twoByTwo = Size(width: 2.0, height: 2.0)

 

值类型的初始化器代理

 

初始化器可以调用其他初始化器来对一个实例进行部分初始化。这个过程,被称为初始化器代理,它能够减少多个初始化器间的代码重复。

初始化器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,因此,初始化器代理的过程相对简单,因为它们只能代理任务给本身提供的其它初始化器。类则不同,它可以继承自其它类。请参阅继承。这意味着类有责任保证其所有继承的存储型属性在初始化时也能正确的初始化。这些责任将在后续章节类的继承和初始化过程中介绍。

对于值类型,你可以使用self.init在自定义的初始化器中引用其它的属于相同值类型的初始化器。并且你只能在initializer调用sef.init。

注:若你对值类型定义一个定制初始化器,你将无法访问该类型的默认初始化器(如果是一个结构体,则无法访问逐一成员初始化器)。这个限制可以防止你在为值类型定义了一个更复杂的、完成了重要准备初始化器之后,别人仍错误的使用了那个自动生成的初始化器。

注:假如你想通过默认初始化器、逐一成员初始化器以及你自己定制的初始化器为值类型创建实例,你应将自己定制的初始化器写到扩展(extension)中,而不是跟值类型定义混在一起。更多相关信息,请查看扩展章节。

以下示例中定义一个结构体Rect,用来展现几何矩形。这个例子需要两个辅助结构体Size和Point,它们各自为其属性提供了初始值0.0:

 

struct Size {

var width = 0.0, height = 0.0

}

struct Point {

var x = 0.0, y = 0.0

}

 

你可以通过以下三种方式为结构体Rect创建实例--使用默认的0值来初始化属性origin和size;使用特定的origin和size实例来初始化,或使用特定的center和size来初始化。在下列结构体Rect定义中,我们为这三种方式提供了三个定制的初始化器:

 

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初始化器init (),在功能上跟未定制初始化器时自动获得的默认初始化器是一样的。这个初始化器是一个空函数,使用一对大括号{}来描述,它没有执行任何定制的初始化过程。 调用该初始化器将返回一个Rect实例,该实例的origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):

 

let basicRect = Rect()

// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

 

第二个Rect初始化器init(origin:size:),在功能上跟结构体在未定制初始化器时获得的逐一成员初始化器是一样的。该初始化器只是简单的将origin和size的参数值赋给对应的存储型属性:

 

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)

 

第三个Rect初始化器init(center:size:)稍微复杂一点。它先通过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))

// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

 

初始化器init(center:size:)可以自己将origin和size的新值赋值到对应的属性中。然而尽量利用现有的初始化器和它所提供的功能来实现init(center:size:)的功能,是更方便、更清晰和更直观的方法。

类的继承和初始化

 

所有类存储属性-包括从其超类继承的类——在初始化过程中必须被赋予一个初始值。

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

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

 

指定初始化器是类中最主要的初始化器。一个指定的初始化器将初始化类中提供的所有属性,并根据超类链往上调用超类的初始化器来实现超类的初始化。

类通常很少指定初始化器,通常情况下,一个类只有一个初始化器。通过所发生的初始化和初始化持续至超类链,被指定的初始化器是“funnel”点。

每一个类都必须至少有一个指定初始化器。在某些情况下,许多类通过继承了超类中的指定初始化器而满足了这个条件。具体内容请参阅后续章节自动初始化器的继承。

便利初始化器是类中比较次要的、辅助型的初始化器。你可以定义便利初始化器来调用同一个类中的指定初始化器,并为其参数提供默认值。你也可以定义便利初始化器来创建一个特殊用途或特定输入的实例。

你应当只在必要的时候为类提供便利初始化器。比方说某种情况下通过使用便利初始化器来快捷调用某个指定初始化器,能够节省更多开发时间并让类的初始化过程更清晰、明确。

初始化器链

 

为简化指定初始化器和便利初始化器之间的调用关系,Swift采用以下三条规则来限制初始化器之间的代理调用:

指定初始化器必须从其直接超类中调用指定初始化器。

便利初始化器必须调用同一类中定义的其它初始化器。一个更方便记忆的方法是:

这些规则说明如下图所示:

initializerDelegation01_2x.png

如图所示,该超类包含一个指定初始化器和两个便利初始化器。其中一个便利初始化器调用了另外一个便利初始化器,而后者又调用了唯一的指定初始化器。满足上述规则2和3。这个超类没有对应的超类,因此规则1不适用。

此图中的子类包含两个指定初始化器和一个便利初始化器。便利初始化器必须调用两个指定初始化器中的任一个,这是因为该便利初始化器只能从同一个类中调用另一个指定初始化器。满足上述规则2和3。这两个指定初始化器均须从超类中调用唯一的指定初始化器,以满足上述规则1的要求。

注:这些规则不会影响你的类用户创建每个类实例的方式。上图中的任何初始化器均可用于为其所属类创建完全初始化实例。这些规则只在实现类的定义时有影响。

下图显示了四个类的更复杂的类层级结构。它演示了指定初始化器是如果在类层级中充当“funnel”的作用,在类的初始化器链上简化了类之间的内部关系:

initializerDelegation02_2x.png

两段式初始化过程

 

Swift中类的初始化过程分两个阶段。在第一阶段,引入所存储各属性的类为每个属性分配一个初始值。一旦确定各存储属性的初始状态后即开始第二阶段,并且各类均有机会在新实例准备运行前对其所存储属性进行进一步定制。

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

注:Swift的两段式初始化过程与Objective-C中的初始化过程类似,主要的区别在于:在第一阶段中,Objective-C将各属性赋值零或null值(0或nil)。Swift的初始化流程则更加灵活,它可允许用户设置定制的初始值,并自如应对某些属性不能以0或nil作为合法默认值的情况。

Swift编译器将执行四种有效的安全检查,以确保两段式构造过程能顺利完成:

指定初始化器必须确保它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给超类中的初始化器。

如上所述,某对象的内存仅在已知其所有存储属性的初始状态后才可被视为完全初始化。为满足此规则,指定初始化器必须确保它所在类引入的属性在它往上代理之前先完成初始化。

指定初始化器必须先向上代理调用超类初始化器,然后再为继承的属性设置新值。否则,指定初始化器赋予的新值将被超类中的初始化器所重写。

在为任一属性(包括同一类定义的属性)复制前,便利初始化器必须先代理调用同一类中的其它初始化器。否则,便利初始化器赋予的新值将被同一类中其它指定初始化器所重写。

在第一阶段初始化完成前,初始化器无法调用任何实例方法、读取任何实例的属性值或将self作为一个值引用。

类未完全实例化,直到第一阶段结束。一旦在第一阶段结束后已知类实例有效,则仅可对属性进行访问,且仅可对方法进行调用。

基于以上四次安全检查,下文介绍如何完成两段式初始化:

阶段1

一个配置的或方便的初始化函数被称为一类。

对该类的新实例的记忆进行配置,该记忆还未被初始化。

为该类配置的初始化函数证实了由该类引进的所有存储属性均有,这些存储属性的记忆现在被初始化。

直到抵达该链的顶端,该配置的初始化函数不干涉超类初始化函数执行相同的This 继续类的继承链。

一旦达到链的顶端, 该链的最后类已确保其所有的存储属性均有值, 该实例的记忆被认为已经全部初始化, 阶段1完成了。

阶段2

从链的顶端往下工作, 该链内的每个配置的初始化函数均有进一步自定义该实例的选项,初始化函数现在可以访问self并修改其属性, 调用其实例类函数等等。

最后,任何该链的方便初始化函数均有自定义选项。

下文介绍阶段1如何针对假设子类和超类搜索初始化调用:

twoPhaseInitialization01_2x.png

本例中,对子类的便利初始化器进行调用后即开始初始化过程。便利初始化器尚不能修改任何属性。它把初始化任务代理给同一类中的指定初始化器。

根据安全检查1,指定初始化器将确保子类的所有属性均有值;然后,该初始化器调用其超类的指定初始化器,以继续在初始化器链上进行初始化。 超类的指定初始化器确保所有超类属性均有值。由于没有更多的超类需要初始化,也就无需继续向上做初始化代理。

当超类的所有属性均有初始值时,实例的内存被认为是完全初始化,而阶段1也已完成。

下述为在阶段2查找相同的初始化调用的过程:

twoPhaseInitialization02_2x.png

初始化器的继承和重写

 

与Objective-C中的子类不同,Swift中的子类不以默认的方式继承其超类的初始化器。Swift的这种机制可以防止一个超类的简单初始化器被一个更专业的子类继承,并被错误的用来创建子类的实例。

假如你希望定制的子类中能实现一个或多个跟超类相同的初始化器--也许是为了完成一些定制的初始化过程--你可以在你定制的子类中提供和重写与超类相同的初始化器。

如果你重写的初始化器是一个指定初始化器,你可以在子类里重写它的实现,并在自定义版本的初始化器中调用超类版本的初始化器。

如果你重写的初始化器是一个便利初始化器,你的重写过程必须通过调用同一类中提供的其它指定初始化器来实现。这一规则的详细内容请参阅初始化器链。

注:与方法、属性和下标不同的是,重写初始化器时无需使用关键字override。

自动初始化器的继承

 

如上所述,子类不通过默认的方式继承其超类初始化器。但是,如果满足特定条件,超类初始化器将被自动继承。实际上,这意味着在多数一般情况下,用户无需重写超类初始化器,并可在安全情况下通过最少的操作步骤继承超类初始化器。

假设要为子类中引入的任意新属性提供默认值,请遵守以下两个规则:

如果子类不对任何指定初始化器进行定义,则它将自动继承其所有超类的指定初始化器。

如果子类可实现其所有超类的指定初始化器-无论是按照规则1继承超类初始化器还是通过定制(作为其定义的一部分)实现-则该子类将自动继承所有超类便利初始化器。

即使你在子类中添加了更多的便利初始化器,这些规则也同样适用。

注:子类可以通过部分满足规则2的方式,使用子类便利初始化器来实现超类的指定初始化器。

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

 

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

 

init( parameters ) {

statements

}

 

便利初始化器也采用相同样式的写法,但需要在关键字init之前写入关键字convenience,并使用空格将其分开:

 

convenience init( parameters ) {

statements

}

 

指定初始化器和便利初始化器的实践

 

下列示例将在实战中展示指定初始化器、便利初始化器和自动初始化器的继承。它定义了包含三个类Food、RecipeIngredient以及ShoppingListItem的类层次结构,并将演示它们的初始化器是如何相互作用的。

该层次中的基类为Food,它是一个简单的用来封装食物名字的类。Food类引入了一个名为name的String属性,并提供两个初始化器来创建Food实例:

 

class Food {

var name: String

init(name: String) {

self.name = name

}

convenience init() {

self.init(name: "[Unnamed]")

}

}

 

下图为Food类的初始化器链:

initializersExample01_2x.png

类没有提供一个默认的逐一成员初始化器,因此, Food类提供了一个接受单一参数name的指定初始化器。这个初始化器可以使用一个特定的名字来创建新的Food实例:

 

let namedMeat = Food(name: "Bacon")

// namedMeat's name is "Bacon"

 

Food类中的初始化器init(name: String)被定义为一个指定构造器,因为它能确保所有新Food实例的中存储型属性都被初始化。。 Food类没有超类。因此,init(name: String)初始化器不需要调用super.init()来完成初始化。

Food类同样提供了一个没有参数的便利构造器init()。这个init()初始化器为新食物提供了一个默认的占位名字,通过代理调用同一类中定义的指定初始化器init(name: String)并给参数name传值[Unnamed]来实现:

 

let mysteryMeat = Food()

// mysteryMeat's name is "[Unnamed]"

 

类层级的第二个类为Food类的子类RecipeIngredent。该RecipeIngredient类构建了食谱中的一味调味剂。它引入了Int类型的数量属性quantity(以及从Food继承过来的name属性),并且定义了两个初始化器来创建RecipeIngredient实例:

 

class RecipeIngredient: Food {

var quantity: Int

init(name: String, quantity: Int) {

self.quantity = quantity

super.init(name: name)

}

convenience init(name: String) {

self.init(name: name, quantity: 1)

}

 

下图为RecipeIngredient类的初始化器链:

initializersExample02_2x.png

RecipeIngredent类拥有一个指定初始化器int(name:String, quantity:Int),它可用于产生新RecipeIngredient实例的所有属性值。这个初始化器首先将传入的quantity参数赋值给属性quantity,该属性也是唯一在RecipeIngredient中新引入的属性。。然后,初始化器将任务向上代理给超类Food的init(name: String)。这个过程满足两段式初始化过程中的安全检查1。

RecipeIngredent还定义了一个便利初始化器init(name:String),可以仅通过name新建一个RecipeIngredent实例。这个便利初始化器假设任意RecipeIngredient实例的quantity为1,因此,不需要显示指明数量即可创建出实例。这个便利初始化器的定义可以让创建实例更加方便和快捷,并且避免了使用重复的代码来创建多个quantity为1的RecipeIngredient实例。这个便利初始化器只是简单的将任务代理给了同一类里提供的指定初始化器。

注:RecipeIngredent提供的init(name:String)便利初始化器与Food中的init(name:String)指定初始化器具有相同参数。即使RecipeIngredent初始化器是便利初始化器,RecipeIngredient依然提供了对所有超类指定初始化器的实现。因此,RecipeIngredent也自动继承其所有超类的便利初始化器。

本例中,RecipeIngredent的超类为Food,其中含有一个名为init()的便利初始化器。因此,RecipeIngredent可继承该初始化器。该继承的init()函数版本跟Food提供的版本是一样的,除了它是将任务代理给RecipeIngredient版本的init(name: String)而不是Food提供的版本。

所有的这三种初始化器都可以用来创建新的RecipeIngredient实例:

 

let oneMysteryItem = RecipeIngredient()

let oneBacon = RecipeIngredient(name: "Bacon")

let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

 

类层级中第三个也是最后一个类是RecipeIngredient的子类-ShoppingListItem。ShoppingListItem构建了购物单中出现的某一种调味料。

购物单中的每一项总是从“unpurchased”状态开始的。为体现这一事实,ShoppingListItem引入了名为purchased的布尔属性,它的默认值是false。ShoppingListItem还添加了计算型属性description,该属性提供ShoppingListItem实例的一些文字描述:

 

class ShoppingListItem: RecipeIngredient {

var purchased = false

var description: String {

var output = "\(quantity) x \(name.lowercaseString)"

output += purchased ? " ✔" : " ✘"

return output

}

}

 

注:ShoppingListItem没有定义初始化器来为purchased提供初始化值,这是因为任何添加到购物单(在此构建)的项的初始状态总是unpurchased。

由于它为所有由其引入的属性提供默认值并且不自动对任何初始化器进行定义,因此,ShoppingListItem将自动从其超类继承所有的指定和便利初始化器。

下图为所有三个类的完整初始化器链:

initializersExample03_2x.png

用户可使用所继承的所有这三个初始化函数来创建新的ShoppingListItem实例:

 

var breakfastList = [

ShoppingListItem(),

ShoppingListItem(name: "Bacon"),

ShoppingListItem(name: "Eggs", quantity: 6),

]

breakfastList[0].name = "Orange juice"

breakfastList[0].purchased = true

for item in breakfastList {

println(item.description)

x orange juice ✔

x bacon ✘

x eggs ✘

 

这时,在一个含有三个ShoppingListItem实例的数字标识符中创建了一个新的名为breakfastList的数组。经推断,该数组类型为ShoppingListItem【】。创建数组后,数组前部的ShoppingListItem名称由“【Unnamed】”更改为“Orange juice”,即标记为已购买。通过对数组中的各项说明进行打印表明,其默认状态已设置为预期状态。

以闭包或函数设置默认属性值

 

如果所存储属性的默认值需要进行自定义或设置,用户可使用闭包或全局函数来为该属性提供自定义默认值。当对属性所属类型的新实例进行初始化时,闭包或函数被调用,并且其返回值被赋值为属性默认值。

以上类别的闭包或函数通常创建一个与属性类型相同的临时值,并调整该值使其可表示所需的初始状态;然后,返回将要作为属性默认值使用的临时值。

本文针对如何将闭包用于提供默认属性值进行了概述:

 

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

}()

}

 

注,闭包的终止花括号后为一对空白圆括号。其命令Swift立即执行闭包。如果漏掉了圆括号,则会将闭包本身分配给属性,而不是闭包的返回值。

注:如果采用闭包来对属性进行初始化,切记当执行闭包时,其余实例并未被初始化。这意味着用户无法从自身闭包内部访问其他任何属性值,即使这些属性有默认值。同时,用户也无法使用隐式self属性或调用任何该实例的类函数。

以下示例对名为Checkerboard的结构进行了定义,该结构对跳棋(也叫国际跳棋)所用的游戏板进行了模拟:

checkersBoard_2x.png

跳棋所用的游戏板为10×10的黑白方块相间的正方形游戏板。Checkerboard结构具有名为boardColors的单一属性用以创建该游戏板,该属性是含有100个Bool值的数组。数组中的true值代表一个黑色方块,false值代表一个白色方块。阵列中的第一项代表游戏板上的左上角方格,最后一项代表游戏板上的右下角方格。

利用一个闭包初始化boardColors数组以设置其颜色值:

 

struct Checkerboard {

let boardColors: Bool[] = {

var temporaryBoard = Bool[]()

var isBlack = false

for i in 1...10 {

for j in 1...10 {

temporaryBoard.append(isBlack)

isBlack = !isBlack

}

isBlack = !isBlack

}

return temporaryBoard

}()

func squareIsBlackAtRow(row: Int, column: Int) -> Bool {

return boardColors[(row * 10) + column

 

当创建好一个新的Checkerboard实例后,执行闭包,并且计算并返回boardColors的默认值。以上示例中的闭包为名为temporaryBoard的临时阵列中游戏板上的每个方格计算并设置适当的颜色,并在完成设置后,将此临时数组作为闭包的返回值返回。所返回的数组值被存储在boardColors中,并且可通过squareIsBlackAtRow效用函数对其进行查询:

 

let board = Checkerboard()

println(board.squareIsBlackAtRow(0, column: 1))

// prints "true"

println(board.squareIsBlackAtRow(9, column: 9))

// prints "false"

 

昵    称:
验证码:

相关文档:

swift
IOS实例
ObjectiveC