14.Swift-构造过程Initialization


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

 

构造过程是通过定义构造器(Initializers)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。

 

类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考析构过程。


一、存储属性的初始赋值

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

 

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

 

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


1、构造器

构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。

下面例子中定义了一个用来保存华氏温度的结构体Fahrenheit,它拥有一个Double类型的存储型属性temperature:

struct Fahrenheit{

    var temperature:Double

    init(){

        temperature = 32.0

    }

}

var f = Fahrenheit()

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


2、默认属性值

如前所述,你可以在构造器中为存储型属性设置初始值;同样,你也可以在属性声明时为其设置默认值。

 

注意:如果一个属性总是使用同一个初始值,可以为其设置一个默认值。无论定义默认值还是在构造器中赋值,最终它们实现的效果是一样的,只不过默认值跟属性构造过程结合的更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承(后续章节将讲到)等特性。

 

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

struct Fahrenheit{

    var temperature = 32.0

}


二、定制化构造过程

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


1、构造参数

你可以在定义构造器时提供构造参数,为其提供定制化构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。

 

下面例子中定义了一个包含摄氏度温度的结构体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)    //100.0

let freezingPointOfWater = Celsius(fromKelvin: 273.15)      //0.0


2、本地和外部参数名

跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。

 

然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名,就相当于在每个构造参数之前加了一个哈希符号#。

 

注意:如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线_来显示描述它的外部名,以此覆盖上面所说的默认行为。


下面定义了一个结构体Color,包含三个常量:red,green和blue。

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) //error


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)


4、可选属性类型

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

 

下面例子中定义了类SurveyQuestion,它包含一个可选字符串属性response:

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()//Do you like cheese?

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


5、在构造过程中修改常量属性

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

 

注意:对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。

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

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


三、默认构造器

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

下面例子中创建了一个类ShoppingListItem,它封装了购物清单中的某一项的属性:名字(name)、数量(quantity)和购买状态 purchase state。


class ShoppingListItem{

    var name:String?

    var quantity = 1

    var purchased = false

}

var item = ShoppingListItem()


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


1、结构体类型的逐一成员构造器

如果结构体自身没有提供任何构造器,它将默认拥有一个逐一成员构造器。即使它没有为它的存储属性提供默认值。

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

struct Size{

    var width = 0.0,height = 0.0

}

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


四、值类型的构造器代理

构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。

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

对于值类型,你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用self.init。

 

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


注意:假如你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制的构造器写到扩展(extension)中,而不是跟值类型定义混在一起。想查看更多内容,请查看扩展章节。


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()//{{x 0.0, y 0.0}, {width 0.0, height 0.0}}

let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))//{{x 2.0, y 2.0}, {width 5.0, height 5.0}}

let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))//{{x 2.5, y 2.5}, {width 3.0, height 3.0}}


五、类的继承和构造过程

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

 

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


1、指定构造器和便利构造器

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

 

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

 

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

 

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



2、 指定构造器和便利构造器语法

类的指定构造器的语法就像值类型的构造器一样:

init(parameters){

statements

}

便利构造器的语法也是一样的风格,只不过多了convenience关键字:

convenience init(parameters){

statements

}


3、构造器链

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

 

规则 1

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

 

规则 2

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

 

规则 3

便利构造器必须最终以调用一个指定构造器结束。

 

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

 

指定构造器必须总是向上代理

便利构造器必须总是横向代理


这些规则可以通过下面图例来说明:

14.Swift-构造过程Initialization_第1张图片


如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。这满足了上面提到的规则2和3。这个父类没有自己的父类,所以规则1没有用到。

 

子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一个类里的其他构造器。这满足了上面提到的规则2和3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则1。

 

注意:这些规则不会影响使用时,如何用类去创建实例。任何上图中展示的构造器都可以用来完整创建对应类的实例。这些规则只在实现类的定义时有影响。

下面图例中展示了一种更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的内部关系。

14.Swift-构造过程Initialization_第2张图片


4、两段式构造过程

Swift的类的构造过程分为两个阶段。第一个阶段,为每个存储属性设置初始值。当每个存储属性值被确定后,第二阶段开始,它给每个类一次在新实例在使用之前进一步定制它们的存储属性的机会。

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


注意:Swift的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值0或空值(比如说0或nil)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以0或nil作为合法默认值的情况。

 

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

 

安全检查 1

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

 

如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。

 

安全检查 2

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

 

安全检查 3

便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。

 

安全检查 4

构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self的值。


以下是两段式构造过程中基于上述安全检查的构造流程展示:

 

阶段 1

某个指定构造器或便利构造器被调用;

完成新实例内存的分配,但此时内存还没有被初始化;

指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;

指定构造器将调用父类的构造器,完成父类属性的初始化;

这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;

当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。

 

阶段 2

从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。

最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。

 

下图展示了在假定的子类和父类之间构造的阶段1: · 

 14.Swift-构造过程Initialization_第3张图片



在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。

 

如安全检查1所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着造器链一直往上完成父类的构建过程。

 

父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要构建,也就无需继续向上做构建代理。

 

一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,而阶段1也已完成。

 

以下展示了相同构造过程的阶段2:

14.Swift-构造过程Initialization_第4张图片


父类中的指定构造器现在有机会进一步来定制实例(尽管它没有这种必要)。

 

一旦父类中的指定构造器完成调用,子类的构指定构造器可以执行更多的定制操作(同样,它也没有这种必要)。

 

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


5、构造器的继承和重载

跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。

假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器--也许是为了完成一些定制的构造过程--你可以在你定制的子类中为那些构造器提供具体实现。

如果你重载的构造器是一个指定构造器,你需要明确的用override关键字声明,并且需要提供具体实现。哪怕是重载一个自动提供的构造器。

 

Swift一样会检查你重载的构造器是否在父类中存在,就像重载属性、方法和附属脚本那样做安全检查。

注意:当你在子类中重载一个指定构造器的时候必须是用override关键字,即使你在子类中的具体实现是一个便利构造器。

相反地,如果你在子类中写了一个在父类中可以匹配得到的便利构造器,你不能在子类中直接调用父类的便利构造器。因此,子类并没有提供父类的构造器的重载,所以在重载父类的便利构造器时,也没必要写override关键字。

 

下面定义了一个基类Vehicle,含有一个numberOfWheels存储属性,默认值是0。它还被一个计算属性description用来生成一个对Vehicle的描述字符串:

class Vehicle{

    var numberOfWheels = 0

    var description:String{

        return "\(numberOfWheels) wheel(s)"

    }

}

Vehicle没有提供构造器,所以自动有一个默认构造器,默认构造器经常用来作为一个类的指定构造器。

let vehicle = Vehicle()

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


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

class Bicycle:Vehicle{

    override init(){

        super.init()

        numberOfWheels = 2

    }

}

Bicycle类定义了一个指定构造器init(),匹配了父类的指定构造器,所以必须在构造器前面加上override关键字。

Bicycle的init()构造器以super.init()开头,调用了父类的init()方法,在Bicycle实例访问numberOfWheels之前给父类了修改numberOfWheels的机会。然后修改了numberOfWheels,从原始值0改为2。


let bicycle = Bicycle()

println("Bicycle:\(bicycle.description)")//Bicycle:2 wheel(s)



6、自动构造器的继承

如上所述,子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重载父类的构造器,并且在尽可能安全的情况下以最小的代价来继承父类的构造器。

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

 

规则 1

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

 

规则 2

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

 

即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。

 

注意:子类可以通过部分满足规则2的方式,使用子类便利构造器来实现父类的指定构造器。


7、指定构造器和便利构造器实战

下面的例子讲展示指定构造器、便利构造器和自动构造器继承的使用,定义了三个类Food、RecipeIngredient和ShoppingListItem,其中Food是基类。

class Food{

    var name:String

    init(name:String){

        self.name = name

    }

    convenience init(){

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

    }

}

let namedMeat = Food(name: "Bacon")


Food提供了指定构造器 init(name:String),确保了所有实例的存储属性都会被完全初始化。Food还提供了便利构造器init(),为name提供了默认值。


第二个类是Food的子类RecipeIngredient,它继承了name属性,新增了quantity属性:

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继承了父类的所有的指定构造器和便利构造器:

let oneMysteryItem = RecipeIngredient()

let oneBeacon = 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 ? " √" : " x"

        return output

    }

}

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 x

6 x Eggs x

*/


六、可失败的构造器


有时候会因为无效的构造器参数值、必须的外部资源的缺失或者是其他原因导致构造过程失败。所以,有时候为一个类、结构体或者枚举定义一个构造过程可以失败的构造器会非常有用的。

在类、结构体和枚举中可以定义一个或多个可失败构造器来支撑可失败构造过程。通过在init后面加上?关键字来标示一个可失败构造器。

注意:不可以同时定义参数类型和名字相同的一个可失败构造器和一个不可失败构造器。

可失败构造器创建一个它构造的类型的一个可选值,在构造失败处写上return nil来标明构造失败了。

注意:严格来讲,构造器不返回任何值。当然,它们的角色是在构造器末尾确保self被完全正确地初始化了。尽管你可以在构造失败的时候写return nil,你不可以在构造成功的时候用return关键字。

下面写了一个Animal结构体,含有一个字符串常量属性species,定义了一个可失败构造器(传入一个字符串参数),如果构造失败触发构造过程失败,否则成功为species赋值:

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{

    println("An animal was initialized with a species of \(giraffe.species)")//An animal was initialized with a species of Giraffe

}


let annoymousCreature = Animal(species: "")

if annoymousCreature == nil{

    println("The annoymousCreature could not be initialized")//The annoymousCreature could not be initialized

}



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{

    println("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{

    println("This is not a defined temperaturte unit,so initialization failed.")//This is not a defined temperaturte unit,so initialization failed.

}


2、带原始值的枚举的可失败构造器

带原始值的枚举自动接收一个可失败构造器init?(rawValue:)当取得到rawValue对应的值时返回取得的值,否则返回nil。

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

}

let unknownUnit = TemperatureUnit(rawValue: "X")

if unknownUnit == nil{

    println("This is not a defined temperaturte unit,so initialization failed.")//This is not a defined temperaturte unit,so initialization failed.

}


3、类的可失败构造器

值类型(结构体和枚举)的可失败构造器可以在实现的时候任何地方触发构建失败。

然后,类的可失败构造器只能在所有的存储属性被赋值并且构造器委托已经出现的地方。

class Product{

    let name:String!

    init?(name:String){

        if name.isEmpty{return nil}

        self.name = name

    }

}

if let bowTie = Product(name: "bow tie"){

    // no need to check if bowTie.name == nil

    println("The product's name is \(bowTie.name)")//The product's name is bow tie

}

Product类定义了一个不能为空的属性name,使用可失败构造器保证它不为空。因为name是可选值,默认为nil,所以在触发构造失败之前它拥有了初始值。


4、可失败构造器的传递

一个类、结构体或者枚举的可失败构造器可以传递给被同一个类、结构体或者枚举的其他可失败构造器,也可以通过父类传递给子类。如果本构造器的代理的构造器失败了,那么本构造器的进程就会立即停止执行。

注意:可失败构造器可以调用其他非可失败构造器。

class CartItem:Product{

        let quantity:Int!

        init?(name: String,quantity:Int) {

            super.init(name: name)//failabel initializer must always perform initializer delegation before triggering an initialization failure

            if quantity < 1 {return nil}

            self.quantity = quantity

        }

}


if let twoSocks = CartItem(name: "sock", quantity: 2){

    println("Item:\(twoSocks.name) , quantity:\(twoSocks.quantity)")//Item:sock , quantity:2

}

//触发本类的失败的构造过程

if let zeroShirts = CartItem(name: "shirt", quantity: 0){

   println("Item:\(zeroShirts.name),quantity:\(zeroShirts.quantity)")

}else{

        println("Unable to initialize zero shirts")//Unable to initialize zero shirts

}

//触发父类的失败构造过程

if let oneUnnamed = CartItem(name: "", quantity: 1){

    println("Item:\(oneUnnamed.name),quantity:\(oneUnnamed.quantity)")

}else{

    println("Unable to initialize zero shirts")//Unable to initialize zero shirts

}


5、可失败构造器的重写

你可以在子类中重写父类的可失败构造器,你也可以将父类的可失败构造器重写为不可失败的构造器。这使得你可以定义一个构造器不可失败的子类,尽管它的父类的构造器是可失败的。

注意:如果你用不可失败构造器重写了可失败构造器,那么不可以在不可失败的构造器中调用可失败的构造器。


下面定义了一个Document类,它有一个name属性,要么是非空的要么是nil:

class Document{

    var name:String?

    //this initializer creates a document with a nil name value

    init(){}

    //this initializer creates a document whit a non-empty name value

    init?(name:String){

        if name.isEmpty{return nil}

        self.name = name

    }

}

下面定义了一个Document的子类AutomaticallyNamedDocument类,它重写了父类的所有指定构造器,重写保证了一个AutomaticallyNamedDocument实例的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比父类更好地支持了通过传入空字符串来构造实例,它不需要失败。因此它通过了一个不可失败的构造器来代替了父类的可失败构造器。


6、可失败构造器init!

你一般用init?的方式来定义一个可失败构造器来创建一个可选实例。你也可以通过init!的方式来定义一个可失败的构造器来创建一个绝对展开的可选实例。

你可以在init?中调用init,反之亦然。你可以用init?来重写init!,反之亦然。你也可以在init中调用init!,尽管init!可能会失败。


七、必须的构造器

通过在构造器的前面加上required关键字来定义一个任何子类都必须实现的构造器:

class SomeClass{

    required init(){

        //initializer implementation goes here

    }

}

每个子类实现的必须构造器前面也必须加上关键字required来表明在构造器链中更远的子类中也必须实现这个必须的构造器。当重写一个必须的构造器时,不需要在前面加上override关键字。

class SomeSubClass:SomeClass{

    required init(){

        //subclass implementation of the required initializer goes here

    }

}

注意:如果通过继承来的构造器可以满足你需求,你不必在子类中提供一个详细的必须的构造器的实现。


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

如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。

这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。

下面列举了闭包如何提供默认值的代码概要:

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,它构建了西洋跳棋游戏的棋盘:

14.Swift-构造过程Initialization_第5张图片


西洋跳棋游戏在一副黑白格交替的 10x10 的棋盘中进行。为了呈现这副游戏棋盘,Checkerboard结构体定义了一个属性boardColors,它是一个包含 100 个布尔值的数组。数组中的某元素布尔值为true表示对应的是一个黑格,布尔值为false表示对应的是一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。

 

boardColor数组是通过一个闭包来初始化和组装颜色值的:



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))//true

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



九、总结

本章介绍了Swift中的构造过程,构造过程通过构造器来实现,构造器不像OC那样返回一个本类型的实例。Swift的类的构造器分为指定构造器和便利构造器两种。并且每一个类都必须拥有至少一个指定构造器。切要遵循: 指定构造器必须总是向上代理

便利构造器必须总是横向代理

的原则。

至于构造器链,介绍的有些繁琐了,如果理解了其他语言的构造过程,就也自然理解了Swift的构造链,其实也就是构造过程中要从最顶端的父类的构造器开始一直到自身的构造器完成结束,对所有属性进行赋值等操作。

需要注意的时是:跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。要继承,必须显式地加上override关键字。 子类虽然不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。

另外,Swift还有可失败构造器和必须的构造器的概念。还可以通过闭包或者函数的方式来为属性设置默认值。



参考:

1、The Swift Programming Language

2、http://www.cocoachina.com/ios/20140612/8787.html

你可能感兴趣的:(Swift)