[翻译]Swift编程语言——初始化

初始化

初始化是准备类、结构体或者枚举的一个实例供使用的过程。这个过程包括给实例上的所有存储属性初始值和实例在使用前必要的操作(setup or initialization)。

实现这个初始化过程通过定义一个构造方法(译者:initializers),构造方法看起来像特殊的方法,可以被调用来创建一个特定类型的新实例。不同于OC的构造方法,Swift的构造方法不返回值。构造方法的主要作用是在类型的实例被首次使用前,确保该实例被正确的初始化。

类的实例同样可以实现一个析构方法(译者:deinitializer),析构方法执行一些定制的清理工作,在该实例被释放前。更多的关于析构方法的信息,参见:Deinitialization。

给存储属性初始值

类和结构体的实例被创建时,它们所有的存储属性必须要被设置恰当的初始值。存储属性的状态不能是不明确的。

可以在构造方法中给存储属性设置初始值,也可以在属性定义时指定默认值。这些行为在接下来的内容会有具体描述。

NOTE

在给存储属性指定默认值、或者在构造方法中给它初始值的时候,数值是被直接设置的,任何属性的观察者都不会被调用。

初始化方法

初始化方法被调用来创建一个特定类型的新实例。初始化方法最简单的形式像一个没有参数的实例方法,使用init关键字定义:

init​() {
    ​    ​// perform some initialization here
    ​}

下面例子中定义了一个新的结构体叫做Fahrenheit来存储表现华氏温度计的刻度。Fahrenheit 结构体有一个Double类型存储属性叫做temperature:

​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,它没有参数,它里面给存储属性一个32.0(华氏温度的冰点表示)的初始值。

默认属性值(Default Property Values)

可以像上面一样在构造方法中给存储属性设置初始值。另外一种选择是在属性定义时指定一个默认属性值(Default Property Values)。通过在定义时指定一个初始值给出默认属性值。

NOTE

如果一个属性总是带有同样的初始值,提供默认属性值比在构造方法中设置值要好。虽然二者结果一样,但默认属性值将属性初始化和属性的定义联系的更加紧密。默认属性值使得初始过程更加简短和明晰,能够根据默认值推断出属性的类型。另外的好处是便于使用默认构造方法和构造方法继承的好处,这在后面的会讲到。

可以在结构体Fahrenheit的temperature定义时采用提供默认值的方式重写这个结构体:

​struct​ ​Fahrenheit​ {
​    ​var​ ​temperature​ = ​32.0
​}

自定义初始化

可以通过输入参数和可选类型或者修改常量属性的方式定制初始化过程,接下来是具体的描述。

初始化参数(Initialization Parameters)

可以将初始化参数作为构造方法定义的一部分,来定义定制的初始化过程需要的值的类型和名称。初始化参数和函数、方法的参数有同样的能力和语法。

下面的例子定义了一个叫做Celsius的结构体,它存储了摄氏温度的表述。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

第一个构造方法有唯一一个初始化参数,这个初始化参数有一个外部名称forFahrenheit和一个本地名称叫做fahrenheit。第二个构造方法有唯一一个初始化参数,它有一个外部名称叫做fromKelvin和一个本地名称叫做kelvin。这两个构造方法都会将它们的参数转化为摄氏制式,并将这个值存储在一个叫做temperatureInCelsius的属性中。

本地和外部参数名

同函数和方法的参数一样,初始化参数可以同时拥有本地名称和外部名称,一个是在构造方法体内使用,一个是在构造方法被调用时使用。

然而,在圆括号前,构造方法没有函数和方法拥有的名称。 所以,构造方法参数的名称和类型扮演了一个非常重要的角色:用来区分调用的是哪个构造方法。因此在没有提供外部名称的情况下,Swift为每一个参数提供一个自动的外部名称。自动的外部名称和本地名称相同,就好像在每个初始化参数前使用了井号一样。

下面的例子定义了一个结构体叫做Color,它有三个常量属性叫做red、green、blue。这些属性存储从0.0到1.0的值来表示红、绿、蓝三种颜色的量。

Color提供了一个构造方法,拥有红、绿、蓝对应的三个Double类型参数。Color提供了第二个构造方法,它有唯一的一个参数叫做white,white会给所有三种颜色设置同一个值。

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

通过给每个构造参数提供命名了的值,每个构造方法都可以创建一个新的Color实例:

​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 - external names are required

没有外部名字的初始化参数

如果不想给一个初始化参数使用外部名称,使用下划线代替一个具体的外部名称来覆盖默认的行为。

这里有一个对早先的Celsius扩展版本,其中添加了一个构造方法,这个构造方法会根据一个已知的摄氏温度创建一个Celsius的实例:

​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

调用Celsius(37.0)意图很明确了,无需外部参数名。因为构造方法写成了init(_ celsius: Double),所以可以在调用时传递一个没有命名的Double数值就行了。

可选参数类型

如果定制类型中有逻辑上可以没有值的存储属性——可能因为在初始化的时候这些属性的值不能被设置,或者因为这些属性在初始化后可以允许是没有值的——那么将这个属性定义为可选类型(optional type)。可选类型的属性会自动被初始化为nil,表明在初始化阶段这些属性是可以没有值的。

下面的例子定义了一个叫做SurverQuestion的类,它有一个可选的String类型的属性叫做resopnse:

class​ ​SurveyQuestion​ {
​    ​var​ ​text​: ​String
​    ​var​ ​response​: ​String​?
​    ​init​(​text​: ​String​) {
​        ​self​.​text​ = ​text
​    }
​    ​func​ ​ask​() {
​        ​println​(​text​)
​    }
​}
​let​ ​cheeseQuestion​ = ​SurveyQuestion​(​text​: ​"Do you like cheese?"​)
​cheeseQuestion​.​ask​()
​// prints "Do you like cheese?"
​cheeseQuestion​.​response​ = ​"Yes,I do like cheese."

一个调查的回应只有在调查做了之后才会知道,所以response属性被声明为了一个String?类型,或者可选String。在一个新的surveyQuestion实例被初始化时,这个属性被自动的初始化为nil,意味着“还没有字符串内容”。

初始化过程中修改常量参数

可以修改常量属性的值在初始化的任何一点,直到初始化结束前它都可以被设置一个明确的值。

NOTE

对于类实例,它的常量属性只能定义它的在初始化阶段被修改,不能被子类修改。

可以修订上面的SurveyQuestion例子,使用常量属性而不是变量属性定义text属性,表明一旦SurveyQuestion实例被创建后,text属性就不能被修改了。尽管text属性现在是常量了,但它仍然可以在类构造方法中被修改:

​class​ ​SurveyQuestion​ {
​    ​let​ ​text​: ​String
​    ​var​ ​response​: ​String​?
​    ​init​(​text​: ​String​) {
​        ​self​.​text​ = ​text
​    }
​    ​func​ ​ask​() {
​        ​println​(​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为结构体或者基本类提供默认的构造方法,给所有它的属性提供默认值但连一个构造方法都不提供。默认构造方法简单的创建了一个各个属性都被设置为默认值的实例。

这个例子定义了一个叫做ShoppingListItem的类,包括名称、数量和购买状态的购物列表:

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

因为ShoppingListItem类的所有属性都有默认值,还因为这个类是一个基本类没有子类,ShoppingListItem 自动获得了一个默认的构造方法实现,创建了一个所有属性被设置为了默认值的实例。(name属性是一个可选String类型的,所以它会自动获得默认值nil,尽管它的值没有提供。)上面例子中通过初始化语法(写做:ShoppingListItem())使用了ShoppingListItem的默认构造方法,创建了这个类的一个实例,将这个新的实例赋值给了一个变量叫做item。

结构体类型的成员构造函数(Memberwise Initializers for Structure Types)

如果没有定制构造方法,结构体类型自动获取一个成员构造方法(Memberwise Initializers)。即使结构图的构造属性没有默认值也可以。

成员构造方法是给一个新的结构体实例初始化成员的简化方式。新实例属性的初始值可以传递给成员构造方法,通过名字。

下面的例子定义了一个叫做Size的结构体,它有两个属性叫做width和height。这两个属性可以被推测是Double类型的,因为它们都被赋了默认值0.0.

Size结构体自动获得一个init(width:height:)成员构造函数,可以使用它来初始化一个Size结构体的实例:

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

值类型的构造方法代理(Initializer Delegation for Value Types)

构造方法可以调用其它构造方法来实现一个实例初始化的一部分。这个过程,叫做构造方法代理(initializer delegation),这样做避免了在多个构造方法中出现重复代码。

构造方法代理的工作规则和什么形式的代理是被允许的,这些对于值类型和类类型而言是不相同的。值类型(结构体和枚举)不支持继承,所以它们的构造方法代理相对简单,因为它们的构造方法只能代理给他们自身的其它构造方法。但是,类可以从其它类继承,就像Inheriatance描述的一样。这就意味着类有额外的功能,要确保在初始化阶段中它继承来的所有存储属性被赋予恰当的值。这些内容将在下面的Class Inheritance and Initialization 中描述。

对于值类型,在定制构造方法时,使用selft.init和对应的值类型来引用其它构造方法。只能在构造方法中调用self.init。

记住,如果给一个值类型定义了定制构造方法,那就不能再访问它的默认构造方法(或者是成员初始化方法,当值类型是结构体的时候)。这是为了避免意外使用默认构造方法,而没有使用提供了必要操作的稍微复杂的自定义构造方法。

NOTE

如果想让值类型的初始化采用默认构造方法和成员构造方法,同时也采用定制的构造方法,那么将自定义的构造方法写在一个扩展(entension)中而不是作为值类型的原始实现的一部分。更多的信息参见:Extensions。

下面例子定义了一个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属性;使用明确的orignin和size;使用明确的中心点和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)

第二个构造方法: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)

第三个构造方法:init(center:size:),稍微复杂一些。开始它会根据center点和size的值计算原点的位置。然后它会调用(或者是委托)init(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:)构造方法最方便(也是意图最明确的)是使用已经存在的构造方法,前提是后者已经提供了需要的功能。

NOTE

不定义init() 和init(origin:size:)构造方法,上面例子的另外的一种实现方式,详见Extensions。

类的继承和初始化

一个类的所有存储属性——包括全部从超类所有属性——必须在初始化过程中被给初始值。

对于类,Swift定义了两种类型的构造方法,来确保所有的存储属性获得一个初始值。它们就是指定构造方法(designated initializers)和便利构造方法(convenience initializers)。

指定构造方法和方便构造方法

指定构造方法是类的主要构造方法。指定构造方法初始化类的所有属性并且调用超类的一个适当的构造方法继续初始化过程,沿着超类链。

类偏向使用较少的指定构造方法,通常只有一个。指定构造方法是初始化发生的切入点,也是初始化过程沿着继承链继续的切入点。

每个类必须至少有一个指定构造方法。某些情况下,继承来自超类的一个或多个指定构造方法也是满足这个条件的,就像下面 Automatic Initializer Inheritance 描述的一样。

方便构造方法是给类提供的第二位的构造方法。可以定义一个方便构造方法来调用同一类的指定构造方法,使用指定构造方法的相同的参数设置,指定默认值。也可以定义一个方便构造方法来创建一个类的实例,来应对特定的使用场景或者输入的值类型。

不必要的情况下,可以不提供方便构造方法。创建构造方法的目的是简化普通的初始化模式或者使得类的初始化意图更明显。

指定构造方法和方便构造方法的语法

类的指定构造方法写法和值类型的简单构造方法一样:

init(parameters) {

    statements

}

方便构造方法的写法风格一样,但是要在init关键字前面添加convenience 修饰符,二者之间用一个空格分隔开:

convenience init(parameters) {

    statements

}

类类型构造方法代理(Initializer Delegation for Class Types)

为了简化指定构造方法和方便构造方法之间的关系,Swift构造方法之间调用代理遵循以下三条规则:

规则1

一个指定构造方法必须调用它的直接集成超类的一个指定构造方法。

规则2

一个方便构造方法必须调用同一个类的另外一个构造方法。

规则3

一个方便构造方法必须最终调用一个指定构造方法。

记住这些的一个简单途径:
指定构造方法总是被委托方;
方便构造方法总是委托方。

这些规则体现在下面的图表中:

这里,超类拥有唯一一个指定构造方法和两个方便构造方法。一个方便构造方法调用了另外一个方便构造方法(这个构造方法本身调用了那个唯一的指定构造方法)。这满足上面所说的第2和第3条规则。因为这里的超类没有更上的超类了,所以第1条规则不适用它。

图中的子类有两个指定构造方法和一个方便构造方法。那个方便构造方法必须要调用两个指定方法中的一个,因为它只能调用同一个类的另外一个构造方法。这满足了上面第2和第3条规则。两个指定构造方法必须调用超类中唯一的那个指定构造方法,来满足前面的第1条规则。

NOTE
这些规则不影响类的使用者创建类的实例。上面图表中任意的构造方法都可以用来创建一个完全初始化的类实例。这些规则只影响怎么样写类的实现。

下面的图表展示了一个更加复杂的四个类的类继承关系。它展示了指定构造方法在继承层级中是如何作为一个切入点,进行类的初始化、简化类的继承链中的相互关系的:

初始化的两个阶段(Two-Phase Initialization)

在Swift中,类的初始化有两个阶段。第一个阶段,每个存储属性被赋予一个初始值。一旦每个存储属性的初始状态被确定下来,第二个阶段就开始了,每个类被给予了一个机会可以进一步的定制它的存储属性,在实例被使用前。

初始化的两个阶段使得初始化过程是安全的,同时给予了在类的继承关系中的每个类以灵活性。初始化的两个阶段阻止了属性的值在被初始化前就被访问,同时阻止了属性意外的被另一个构造方法赋值。

NOTE
Swift的初始化两阶段和OC中的初始化类似。主要的区别在第一阶段,OC会给每个属性赋值0或者null(就像0或者nil)。Swift的初始化更加灵活,可以设置自定义的初始值,也可以应对0或者nil不是合法默认值的类型。

Swift的编译器会做四项有用的安全检查,来确保初始化的两个阶段没有错误:

安全检查1

在将自己委托给超类构造方法之前,一个指定构造方法必须确保它自身定义的所有属性被初始化了。

像上面陈述的一样,只有当一个对象的所有存储属性初始状态都确定了才会为它分配内存。为了使这个规则被满足,一个指定构造方法必须确保在进入(初始化)链的下一个环节前所有它自己的属性都被初始化了。

安全检查2

在给继承来的属性赋值前,一个指定构造方法必须委托超类的构造方法。如果不这样做,指定构造方法的赋值会被超类的初始化方法覆盖掉。

安全检查3

在给任何属性(包括同一个类定义的属性)赋值前,一个方便构造方法必须委托另外一个构造方法。如果不这样做,方便构造方法的赋值会被它自身类的指定构造方法覆盖掉。

安全检查4

一个构造方法不能调用任何实例方法、读取任何实例属性,在初始化的第一阶段结束前不能使用self。

直到第一阶段结束,类的实例才是完全有效的。在第一阶段结束时,类实力是合法的了,属性只能被访问、方法只能被调用。

依据上面四条安全检查,这里展示了初始化两阶段是如何进行的:

阶段1

  • 一个指定构造方法或者方便构造方法被一个类调用。

  • 给那个类的一个实例分配了内存。那块内存还没有被初始化。

  • 那个类一个指定构造方法确保那个类定义的所有属性都有值了。现在那些存储属性的内存被初始化了。

  • 那个指定构造方法调用超类的一个构造方法对超类的存储属性做同样的事情。

  • 直到到达继承链的顶端,这个行为一直向上进行。

  • 一旦到达继承链的顶端,而且最后一个雷确保自身的存储属性都有值了,这个实例的内存就被认为完成初始化了,第一阶段结束。

阶段2

  • 从继承链顶端向下工作,链上的每个指定构造方法有进一步定义实例的选择。构造方法现在可以访问self,可以修改它的属性,可以调用他的实例方法,等等。
  • 最终,链上的任意一个方便构造方法有定义实例的选择,也可以用self。

这里是阶段1在假设的子类和超类如何中如何查找一个初始化调用:

这个例子中,初始化过程以调用子类上的方便构造方法开始。这个方便构造方法还不能修改任何属性。它委托了同一个类的指定构造方法。

那个指定构造方法确保所有的子类属性都有值了,就像安全检查1种要求的一样。然后它调用它超类的一个指定构造方法,在继承链上继续进行初始化。

超类的指定构造方法确保所有的超类属性都有值了。这里没有更上面的超类要初始化,所以不必进一步向上委托。

一旦超类所有的属性都有初始值了,对应的内存就被认为初始化完了,第一阶段结束。

这里是同一个初始化的第二阶段:

超类的指定构造方法有一个进一步定制实例的机会(尽管它不必一定这样做)。

一旦超类的指定构造方法结束,子类的指定构造方法可以做额外的定制(同样,尽管它也不必一定这样做)。

最终,一旦子类的指定构造方法结束,发起最原始调用的方便构造方法可以做额外的定制了。

构造方法的继承和重写

和OC中的子类不同,Swift的子类不会默认继承它们超类的构造方法。Swift直接避免了出现这样的情形:一个超类的简单构造方法被一个相对特殊的子类构造方法继承,这个子类构造方法被用来创建出了一个并不完全或者正确初始化的实例。

NOTE
超类构造方法在特定环境中被继承,只在这样做是安全而且恰当的时候才行。更多的信息参看下面的: Automatic Initializer Inheritance。

如果子类想要针对一个或多个超类的构造方法进行定制,需要提供这些构造方法的子类实现。

当子类构造方法写的和超类的一个指定构造方法一致时,实际上是重写了那个指定构造方法。因此需要在子类的构造方法定义前写上override 修饰符。即使重写一个自动获得的默认构造方法也要这么做,参见: Default Initializers.

像重写属性、方法或者脚本一样,override修饰符督促Swift检查超类是否拥有一个与之匹配的指定构造方法可以被重写,检查重写的构造方法参数。

NOTE
重写超类的指定构造方法需要一直都写override修饰符,即使子类实现时一个方便构造方法。

相反的,如果子类的一个构造方法和超类的方便构造方法匹配,超类的方便构造方法永远不会被子类直接调用,就像 Initializer Delegation for Class Types描述的规则一样。因此,子类没有(严格说来)提供一个超类构造方法的重写。结果就是不必因为提供了和超类方便构造方法匹配的构造方法,而使用override修饰符。

下面的例子定义了一个基本类叫做Vehicle。这个类定义了一个存储属性叫做numberOfWheels,这个属性有一个默认的整型值0.numberOfWheels属性被一个计算属性description调用,来创建一个Stirng类型的交通工具描述内容:

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

Vehicle类给它唯一的一个存储属性提供了一个默认值,没有提供任何定制的构造方法。作为结果,它会自动获得一个默认的构造方法,就像Default Initializers描述 的一样。默认的构造方法(当有用时)总是类的制定构造方法,可以用来创建创建Vehicle类的一个实例,给numberOfWheels赋值为0:

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

下面的类型定义Vehicle的一个子类叫做Bicycle:

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

子类Bicycle自己定义了一个指定构造方法:init()。这个制定构造方法和Bicycle的超类的一个指定构造方法匹配,所以Bicycle版本的构造方法就需要用override修饰符标记。

Bicycle的init()构造方法开始调用了super.init(),就是调用了Bicycle的超类Vehicle的默认构造方法。这样确保了从Vehicle继承来的numberOfWheels属性在被Bicycle修改前就已经被初始化好了。调用super.init()之后,numberOfWheels的初始值会被新值2替换。

如果创建了一个Bicycle实例,可以调用继承来的description计算属性查看它的numberOfWheels属性被修改成了什么:
let​ ​bicycle​ = ​Bicycle​()
​println​(​”Bicycle: ​(​bicycle​.​description​)​”​)
​// Bicycle: 2 wheel(s)

NOTE
初始化阶段子类可以修改继承来的变量属性,但是不能修改继承来的常量属性。

继承来的自动构造方法(Automatic Initializer Inheritance)

像上面提到的一样,子类不能默认继承它超类的构造方法。然而,特定条件下超类的构造方法会自动被继承。实践中,这意味着多数情况下不需要重写构造方法,在安全的前提下可以继承超类的构造方法,做到代价最小。

假设子类新加的属性都提供了默认值,下面两条规则要遵守:

规则1

如果子类没有定义任何指定构造方法,它会默认继承超类的指定构造方法。

规则2

如果子类提供了它超类所有指定构造方法的实现,或是通过规则1种的继承而来,或是在它的定义中提供了自定义实现,那么子类会自动继承超类的全部方便构造方法。

即使子类添加了方便构造方法这些规则也适用。

NOTE
一个子类可以实现超类的指定构造方法作为子类的方便构造方法,部分满足规则2。(??????)

指定构造方法和方便构造方法实战(Designated and Convenience Initializers in Action)

下面的例子展示了指定构造方法、方便构造方法和自动构造方法继承的实战。这个例子定义了有继承关系的三个类叫做Food、RecipeIngredient和ShoppingListItem,展示了它们的构造方法是如何相互作用的。

继承谱系中的基类叫做Food,它是食品名称的基类。Food类定义了唯一一个String类型的属性叫做name,提供了两个构造方法来创建Food实例:

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

图表展示了Food类的构造方法(调用)链:

类没有默认的成员构造方法,因此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()构造方法提供了一个默认的占位名字作为一个新的食物名字[Unnamed],委托Food类的init(name: String)构造方法:

let​ ​mysteryMeat​ = ​Food​()
​// mysteryMeat's name is "[Unnamed]"

继承谱系中的第二个类是Food的子类,叫做RecipeIngredient。RecipeIngredient类表现的是一道菜的菜谱。它定义了一个Int属性叫做quality(在继承Food来的name属性基础上添加的)和两个创建RecipeIngredient 实例的构造方法:

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

下面的图反应了RecipeIngredient类的初始化链:

RecipeIngredient 类有唯一一个指定构造方法:init(name: String,quantity: Int),它会将一个RecipeIngredient类的新实例的全部属性初始化。这个构造方法开始用quantity参数给quantity属性赋值,quantity是RecipeIngredient唯一自己定义的属性。然后,委托Food类的构造方法init(name: String)。这个过程满足前面Two-Phase Initialization一节中的安全检查1。

RecipeIngredient 同时也定义了一个方便构造方法: init(name: String),它被用来只根据一个名字创建一个RecipeIngredient 实例。这个构造方法会将所有的RecipeIngredient实例的数量都赋值为1.这个方便构造方法使得创建一个RecipeIngredient实例更加快捷和方便,避免了构造若干个数量都是1的duplication实例时产生的代码重复。这个方便构造方法仅仅是委托了类自身的指定构造方法,传递给quantity的值为1.

RecipeIngredient提供的init(name: String)方便初始化方法和超类Food中的指定构造方法init(name: String)带的参数是一样的。因为这个方便构造方法重写了它超类的指定构造方法,它必须被override修饰符标记(和Initializer Inheritance and Overriding描述的一样)。

尽管RecipeIngredient提供了init(name: String)初始化方法作为一个方便构造方法,RecipeIngredient仍然提供了它超类的指定构造方法。因此,RecipeIngredient自动继承了所有超类的方便构造方法。

这个例子中,RecipeIngredient的超类是Food,Food只有唯一一个方便构造方法叫做init()。这个构造方法因此被RecipeIngredient继承。继承版本的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类表现菜谱在购物列表上的内容。

购物列表中的每一项开始都是“未购买”。为了表现这些,ShoppingListItem 定义了一个叫做purchased的布尔类型的属性,它有一个默认的false属性。ShoppingListItem 同时添加 了一个计算属性叫做description,它给ShoppingListItem 实例提供一个文本描述内容:

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

NOTE
ShoppingListItem 没定义一个能够初始化purchased属性的构造方法,因为购物列表(就像例子中的)通常开始时都是没有购买的。

因为给所有自己定义的属性都赋予了默认值而且没有定义任何构造方法,ShoppingListItem 自动继承它的超类的所有制定构造方法和方便构造方法。

下图展示了所有这三个类的构造链:

可以使用三个继承来的构造方法创建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​)
​}
​// 1 x Orange juice ✔
​// 1 x Bacon ✘
​// 6 x Eggs ✘

这里,一个新的数组breakfastList 被用字面包含的方式创建,其中是三个ShoppingListItem 类实例。数组的类型是[ShoppingListItem]。数组被创建后,数组中第一项的名字被从“[Unnamed]”修改为“Orange juice”同时被标记为已经购买。打印它们每一项的描述,显示它们的默认状态按照预期的被修改了。

可以失败的构造方法

定义一个可以初始化失败的类、结构体或者枚举,有时是非常有用的。这类失败可能由错误的初始化参数、缺失必要的外部资源或者其他阻止初始化成功的条件而触发。

为了应对引起初始化错误的条件,可以给一个类、结构体或者枚举定义一个或多个可以失败的构造方法。在init关键字后添加一个问号(init?)就定义了一个可以失败的构造方法。

NOTE
不能定义拥有相同参数类型和名称的可以失败的和不可失败的构造方法。

可以失败构造方法创建了一个可选类型。当初始化失败被触发时,在可以失败的构造方法中写上:return nil。

NOTE
严格说来,构造方法并不返回一个值。它们的作用是确保在初始化结束后,self被完全而且正确的初始化。尽管写return nil 触发初始化失败,但是不能使用return关键字表示初始化成功。

下面例子定义了一个叫做Animal的结构体,它有一个Stirng类型的常量属性叫做species。Animal结构体定义了一个可以失败的构造方法,它有唯一一个参数叫做species。这个构造方法检查传递进来的species参数值是不是一个空的字符串。如果一个空的字符串被发现,就触发初始化失败。否则,species属性的值被设置,初始化成功:

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

可以使用这个可以失败的构造方法创建Animal的一个新实例并且检查初始化是否成功:

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

如果传递一个空的字符串给可以失败的构造方法中的species参数,那个构造方法触发一个初始化失败:

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

NOTE
检查一个空的字符串(比如“”而不是“Giraffe”)和检查表示没有值的可选String值是不一样的。上面的例子中,一个空的字符串“”是合法的,不是可选的String。但是,一个动物的species属性是一个空字符串是不恰当的。为了模拟这个限制,当一个空字符串被发现时,可以失败的构造方法触发初始化失败。

枚举的可以失败构造方法

可以根据一个或多个参数使用可以失败构造方法选择一个合适的枚举成员。当提供的参数不匹配合适枚举成员,这种构造方法可以失败。

下面的例子定义了一个叫做TemperatureUnit的枚举类型,它有三个可能的状态(Kelvin,Celsius和Fahrenheit)。根据一个描述温度符号的Character类型值,一个可以失败的构造方法被用来挑选一个合适的枚举成员:

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

采用原始值的枚举的可以失败的构造方法(Failable Initializers for Enumerations with Raw Values)

有原始值的枚举自动获得一个可以失败的构造方法: init?(rawValue:),这个构造方法有一个叫做rawValue的参数,它的类型和原始值的类型一致,如果找到匹配的枚举成员会选择它,在不存在匹配值时会触发初始化失败。

下面重写上面的TemperatureUnit 例子,采用Character类型的原始值,使用init?(rawValue:)构造方法:

​enum​ ​TemperatureUnit​: ​Character​ {
​    ​case​ ​Kelvin​ = ​"K"​,​Celsius​ = ​"C"​,​Fahrenheit​ = ​"F"
​}
​
​let​ ​fahrenheitUnit​ = ​TemperatureUnit​(​rawValue​: ​"F"​)
​if​ ​fahrenheitUnit​ != ​nil​ {
​    ​println​(​"This is a defined temperature unit,so initialization succeeded."
​
​let​ ​unknownUnit​ = ​TemperatureUnit​(​rawValue​: ​"X"​)
​if​ ​unknownUnit​ == ​nil​ {
​    ​println​(​"This is not a defined temperature unit,so initialization failed."

类的可以失败的构造方法(Failable Initializers for Classes)

值类型(就是结构体或者枚举)的可以失败的构造方法会触发一个初始化失败,在其构造方法实现 的任意一个时间。在上面的Animal结构体中,构造方法触在方法很开头的地方就发一个初始化失败,在species属性被设置之前。

然而对于类,可失败的构造方法只能在这个类定义的所有存储属性被给予初始值而且构造方法代理已经发生之后才能触发一个初始化失败。

下面的例子展示了在一个类的可失败构造方法中如何使用一个自拆包可选属性满足这个要求:

​class​ ​Product​ {
​    ​let​ ​name​: ​String​!
​    ​init​?(​name​: ​String​) {
​        ​if​ ​name​.​isEmpty​ { ​return​ ​nil​ }
​        ​self​.​name​ = ​name
​    }
​}

上面Product类的定义和前面Animal结构体很像。Product类有一个常量属性叫做name,它不能被赋予一个空字符串。为了做到这个要求,Product类使用了一个可以失败的构造方法。

然而,Product是一个类而不是一个结构体。这意味着和Animal不同,Product类的可失败构造方法在触发初始化失败之前,必须给name属性提供一个初始值。

上面的例子中,Product类的那么属性被定义为隐式自解包的可选字符串类型(String!)。因为它是可选类型的,这意味着name属性在初始化阶段被赋予一个特定值之前,有一个默认值nil。作为一个结果,在构造方法中给name属性赋值之前,如果传递进来一个空字符串,Product的可失败构造方法会在构造方法一开始就触发构造失败。

因为name属性是常量,如果初始化过程成功了,可以确信这个属性是非空(译者:non-nil)的。尽管它的定义是一个默认自动拆包的可选类型,但可以尽管放心的使用它拆包后的值,无需做是否为nil的检查:

​if​ ​let​ ​bowTie​ = ​Product​(​name​: ​"bow tie"​) {
​    ​// no need to check if bowTie.name == nil
​    ​println​(​"The product's name is ​\(​bowTie​.​name​)​"​)
​}
​// prints "The product's name is bow tie"

可以失败构造方法的传递(Propagation of Initialization Failure)

一个类、结构体或者枚举的可失败构造方法可以委托同一类、结构体或者枚举的可失败构造方法。类似的,子类的可失败构造方法可以委托超类的可失败构造方法。

两种情况下,委托的构造方法如果失败,整个初始化过程就立即失败了,进一步的初始化代码不会被执行。

NOTE
一个可以失败的构造方法同样可以委托另外一个构造方法。这个用法可以给一个已经存在的不会失败的构造方法添加一个潜在失败的状态。

下面例子定义了一个Product的子类叫做CartItem。CartItem模拟了在线购物车中的一项。CartItem定义了一个常量存储类型叫做quantity并且确保它始终有值而且至少为1:

​class​ ​CartItem​: ​Product​ {
​    ​let​ ​quantity​: ​Int​!
​    ​init​?(​name​: ​String​,​quantity​: ​Int​) {
​        ​super​.​init​(​name​: ​name​)
​        ​if​ ​quantity​ < ​1​ { ​return​ ​nil​ }
​        ​self​.​quantity​ = ​quantity
​    }
​}

quantity属性是隐式自拆包的整型(Int!)。像Product类中的name属性一样,这意味着quantity属性在初始化阶段没有被赋值前,有一个默认值nil。

CartItem的可失败构造方法一开始委托了它超类Product的构造方法:init(name:)。这么做遵循了这样一条规则:可以失败的构造方法在触发一个初始化失败前先进行构造方法委托。

如果因为一个空的name数值导致超类初始化失败,整个初始化过程就立即失败,而且后续的初始化代码不会被执行。如果超类初始化成功,CartItem的构造方法会验证它收到的quantity是否大于等于1.

如果使用一个不为空的名字和不小于1的数量创建一个CartItem实例,初始化会成功:

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

如果试着用0做quantity的值创建一个CartItem实例,CartItem的构造方法会引起初始化失败:

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

重写可失败构造方法(Overriding a Failable Initializer)

在子类中可以重写超类的可失败构造方法,就像其他构造方法一样。或者,可以将超类的可失败构造方法重写为子类的不可失败构造方法。这样,能够定义子类的一个不会失败的构造方法,尽管超类中的初始化过程是允许失败的。

记住,如果用子类的不可失败构造方法重写了一个超类的可失败构造方法,子类的构造方法就不能委托超类的构造方法了。一个不可失败的构造方法永远不能(将自己)委托(给)一个可以失败的构造方法。

NOTE

可以用一个不可失败的构造方法重写一个可失败的构造方法,而不是相反的。

下面的类定义了一个叫做Document。这个类模拟了一个文件,它的name属性可以是一个非空的字符串或者是nil,但不能是空字符串:

​class​ ​Document​ {
​    ​var​ ​name​: ​String​?
​    ​// this initializer creates a document with a nil name value
​    ​init​() {}
​    ​// this initializer creates a document with a non-empty name value
​    ​init​?(​name​: ​String​) {
​        ​if​ ​name​.​isEmpty​ { ​return​ ​nil​ }
​        ​self​.​name​ = ​name
​    }
​}

接下来的例子定义了一个叫做AutomaticallyNameDocument的Document子类。AutomaticallyNamedDocument子类重写了Document类的两个指定构造方法。这些重写确保了一个AutomaticallyNamedDocument的实例在没有被初始化的时候或者一个空字符串被传给构造方法init(name:),它的name有一个“[Untitled]”的名字:

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

AutomaticallyNamedDocument重写超类的可失败构造方法init?(name:)为一个不可失败的构造方法:init(name:)。因为AutomaticallyNamedDocument处理空字符串的方式和它的超类不同了,它的构造方法不会失败,所以它提供了一个不可失败的构造方法版本作为替代。

init!可失败构造方法(The init! Failable Initializer)

可以定义一个可以失败的构造方法创建一个可选的对应类型的实例:采用init关键字后跟一个问号(init?)。同样的,可以定义一个可以失败的构造方法来创建一个可以隐式拆包对应类型实例。要做到这样,只需要用叹号替换问号即可(init !)。

可以init?委托给init!,反之亦然,可以用init!重写init?,反之亦然。init可以委托给init!,尽管这么做在init!失败的时候会触发断言。

必须的构造方法(Required Initializers)

在一个类的构造方法定义前写上required修饰符,这表明这个类的所有子类都要实现这个构造方法:

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

每个子类的必须构造方法实现前,也需要required修饰符,这表明这个构造方法的需求会继续像子类的子类传递。一个必须的制定构造方法(a required designated initializer)不需要使用override修饰符:

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

NOTE
如果可以通过继承可以满足必要性的需求,可以不提供必要构造方法的实现。

为一个 闭包或者函数设置一个默认属性值(Setting a Default Property Value with a Closure or Function)

如果一个存储属性的默认值需要定制,可以使用一个闭包或者全局函数来提供定制的默认值。当包含这个存储属性的那个类型的实例被构造时,闭包或者函数就会别调用,闭包或者函数的返回值会做为属性的默认值赋值给属性。

这类的闭包和函数创建了和属性类型相同的值,裁切那个值,然后返回临时值作为属性的默认值。

这里是一个如何使用闭包提供默认值的概要:

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

NOTE

如果使用闭包初始化一个属性,要记得在闭包执行时,实例的其他部分还没有初始化。这意味着在闭包中不能访问其他属性的值,如果这些属性有默认值也不行。同样不能使用self属性,或者调用任何实例方法。

下面的的例子定义了一个结构体叫做Checkerboard(译者:西洋跳棋盘),它模拟了西洋跳棋(也没称作国际跳棋)的棋盘:

西洋跳棋在一个10*10的格子的棋盘上 玩,棋盘上用黑白两种颜色标识相邻的格子。为了表现棋盘,Checkerboard结构体定义了唯一的一个属性叫做boardColors,这个属性是一个有100个Bool值的数组。数组中的true表示的是一个黑色的方格,flase表示的是一个白色的方格。数组最开始的项表示的是棋盘上左上角的方格,最后一个项比啊是的是棋盘上右下角的方格。

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 实例被创建,那个闭包就被执行一次,boardColor的默认值被计算和返回。上面的闭包计算和设置每个方格对应的颜色,结果存放在临时数组temporaryBoard中,当计算完毕后,这个临时数组会作为闭包的返回值返回。返回的数组值存储在boardColors中,可以使用squareIsBlackAtRow 查询其中的内容:

let​ ​board​ = ​Checkerboard​()
​println​(​board​.​squareIsBlackAtRow​(​0​,​column​: ​1​))
​// prints "true"
​println​(​board​.​squareIsBlackAtRow​(​9​,​column​: ​9​))
​// prints "false"

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。