Swift3.0P1 语法指南——构造器
参考:http://wiki.jikexueyuan.com/project/swift/chapter2/14_Initialization.html
1、构造过程(Initialization)
构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包括设置实例中每一个存储属性的值,以及为其执行必要的准备和初始化任务。
构造过程是通过定义构造器(Initializers
)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类的实例也可以通过定义析构器(deinitializer
)在实例释放之前执行特定的清除工作。
2、设置存储属性的初始值
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
注意:在构造器设置存储属性的值,或者定义存储属性默认值,这些都会直接设置值,不会调用属性观察器。
(1)构造器
构造器在创建某特定类型的新实例时调用。最简形式如下:
1 init() { 2 // perform some initialization here 3 }
下面是一个结构体的初始化示例:
1 struct Fahrenheit { 2 var temperature: Double 3 init() { 4 temperature = 32.0 5 } 6 } 7 var f = Fahrenheit() 8 print("The default temperature is \(f.temperature)° Fahrenheit") 9 // prints "The default temperature is 32.0° Fahrenheit"
(2)默认初始值
可以在属性声明时为其设置默认值。
注意:如果一个属性总是使用同一个初始值,可以为其设置一个默认值,而不是在构造器中赋值。尽管它们实现的效果是一样的,只不过默认值将属性的初始化和属性的声明结合的更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性。
1 struct Fahrenheit { 2 var temperature = 32.0 3 }
3、自定义构造过程
可以通过输入参数和可选属性类型来定义构造过程,也可以在构造过程中修改常量属性。
(1)构造参数
可以在定义构造器时提供构造参数,为其提供自定义构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。
1 struct Celsius { 2 var temperatureInCelsius: Double 3 init(fromFahrenheit fahrenheit: Double) { 4 temperatureInCelsius = (fahrenheit - 32.0) / 1.8 5 } 6 init(fromKelvin kelvin: Double) { 7 temperatureInCelsius = kelvin - 273.15 8 } 9 } 10 let boilingPointOfWater = Celsius(fromFahrenheit: 212.0) 11 // boilingPointOfWater.temperatureInCelsius is 100.0 12 let freezingPointOfWater = Celsius(fromKelvin: 273.15) 13 // freezingPointOfWater.temperatureInCelsius is 0.0
(2)参数名和变量标签(Parameter Names and Argument Labels)
跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。
如果你在定义构造器时没有提供参数的外部名,Swift 会为每个构造器的参数自动生成一个跟内部名相同的外部名。
1 struct Color { 2 let red, green, blue: Double 3 init(red: Double, green: Double, blue: Double) { 4 self.red = red 5 self.green = green 6 self.blue = blue 7 } 8 init(white: Double) { 9 red = white 10 green = white 11 blue = white 12 } 13 }
两个构造器都可以用来创建实例:
1 let magenta = Color(red: 1.0, green: 0.0, blue: 1.0) 2 let halfGray = Color(white: 0.5)
注意,不能不用外部名来调用这些构造器,如果外部名被定义,就一定要使用,省略将会造成编译错误:
1 let veryGreen = Color(0.0, 1.0, 0.0) 2 // this reports a compile-time error - external names are required
(3)没有外部名的构造器
如果使用构造器时不想用外部名,则定义参数的时候用_代替显式外部名,覆盖默认的外部名。
1 struct Celsius { 2 var temperatureInCelsius: Double 3 init(fromFahrenheit fahrenheit: Double) { 4 temperatureInCelsius = (fahrenheit - 32.0) / 1.8 5 } 6 init(fromKelvin kelvin: Double) { 7 temperatureInCelsius = kelvin - 273.15 8 } 9 init(_ celsius: Double) { 10 temperatureInCelsius = celsius 11 } 12 } 13 let bodyTemperature = Celsius(37.0) 14 // bodyTemperature.temperatureInCelsius is 37.0
(4)可选属性类型
可选类型的属性将自动初始化为空nil
,表示这个属性在构造过程中还没有值。
1 class SurveyQuestion { 2 var text: String 3 var response: String? 4 init(text: String) { 5 self.text = text 6 } 7 func ask() { 8 print(text) 9 } 10 } 11 let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?") 12 cheeseQuestion.ask() 13 // prints "Do you like cheese?" 14 cheeseQuestion.response = "Yes, I do like cheese."
调查问题在问题提出之后,我们才能得到回答。所以我们将属性回答response
声明为String?
类型,或者说是可选字符串类型optional String
。当SurveyQuestion
实例化时,它将自动赋值为空nil
,表明暂时还不存在此字符串。
(5)构造过程中给常量属性赋值
只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
注意:对某个类实例(class instances)来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
1 class SurveyQuestion { 2 let text: String 3 var response: String? 4 init(text: String) { 5 self.text = text 6 } 7 func ask() { 8 print(text) 9 } 10 } 11 let beetsQuestion = SurveyQuestion(text: "How about beets?") 12 beetsQuestion.ask() 13 // prints "How about beets?" 14 beetsQuestion.response = "I also like beets. (But not with cheese.)"
4、默认构造器
Swift 为所有属性已提供默认值、并且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。
1 class ShoppingListItem { 2 var name: String? 3 var quantity = 1 4 var purchased = false 5 } 6 var item = ShoppingListItem()
由于ShoppingListItem
类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器(尽管代码中没有显式为name
属性设置默认值,但由于name
是String?类型,它将默认设置为nil
)。上面例子中使用默认构造器创造了一个ShoppingListItem
类的实例(使用ShoppingListItem()
形式的构造器语法),并将其赋值给变量item
。
(1)结构体类型的逐一成员构造器
如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。
和默认构造器不同,结构体接收逐一成员构造器即使它有些存储属性还没有初始值。
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
1 struct Size { 2 var width = 0.0, height = 0.0 3 } 4 let twoByTwo = Size(width: 2.0, height: 2.0)
5、值类型的构造器代理(Initializer Delegation for Value Types)
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
构造器代理的实现规则和形式在值类型和类类型中有所不同。
值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。
类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
对于值类型,在写自定义的构造器时可以用self.init引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用self.init。
如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。
注意:如果你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制的构造器写到扩展(extension
)中,而不是跟值类型定义混在一起。
1 struct Size { 2 var width = 0.0, height = 0.0 3 } 4 struct Point { 5 var x = 0.0, y = 0.0 6 }
你可以通过以下三种方式为Rect
创建实例--使用默认的0值来初始化origin
和size
属性;使用特定的origin
和size
实例来初始化;使用特定的center
和size
来初始化。这三种方式对应的构造器:
1 struct Rect { 2 var origin = Point() 3 var size = Size() 4 init() {} 5 init(origin: Point, size: Size) { 6 self.origin = origin 7 self.size = size 8 } 9 init(center: Point, size: Size) { 10 let originX = center.x - (size.width / 2) 11 let originY = center.y - (size.height / 2) 12 self.init(origin: Point(x: originX, y: originY), size: size) 13 } 14 }
第一个Rect
构造器init()
,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。调用这个构造器将返回一个Rect
实例,它的origin
和size
属性都使用定义时的默认值Point(x: 0.0, y: 0.0)
和Size(width: 0.0, height: 0.0)
:
1 let basicRect = Rect() 2 // basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
第二个Rect
构造器init(origin:size:)
,在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的。这个构造器只是简单地将origin
和size
的参数值赋给对应的存储型属性:
1 let originRect = Rect(origin: Point(x: 2.0, y: 2.0), 2 size: Size(width: 5.0, height: 5.0)) 3 // 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
值赋值到对应的属性中:
1 let centerRect = Rect(center: Point(x: 4.0, y: 4.0), 2 size: Size(width: 3.0, height: 3.0)) 3 // centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
构造器init(center:size:)
可以自己将origin
和size
的新值赋值到对应的属性中。然而尽量利用现有的构造器和它所提供的功能来实现init(center:size:)
的功能,是更方便、更清晰和更直观的方法。
6、类的继承和构造过程
类里面的所有存储型属性--包括所有继承自父类的属性--都必须在构造过程中设置初始值。
Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,分别是指定构造器和便利构造器。
(1)指定构造器(Designated Initializers)和便利构造器(Convenience Initializers)
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。
便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
(2)指定构造器和便利构造器语法
指定构造器的语法和值类型的一般构造器相同:
1 init(parameters) { 2 statements 3 }
便利构造器则需要在init关键字前面加上convenience关键字:
1 convenience init(parameters) { 2 statements 3 }
(3)类类型的构造器代理(Initializer Delegation for Class Types)
为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用:
-
指定构造器必须调用其直接父类的的指定构造器。
-
便利构造器必须调用同一类中定义的其它构造器。
-
便利构造器必须最终调用一个指定构造器。
一个更方便记忆的方法是:
- 指定构造器必须总是向上代理
- 便利构造器必须总是横向代理
注意:这些规则不会影响使用时,如何用类去创建实例。这些规则只在实现类的定义时有影响。
(4)两段式构造过程(Two-Phase Initialization)
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
。
(5)构造器的继承和重写
跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。
注意: 父类的构造器仅在确定和安全的情况下被继承。
假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器,也许是为了完成一些定制的构造过程,你可以在你定制的子类中提供和重写与父类相同的构造器。
当你写一个和父类指定构造器相同的子类构造器时,你需要重写这个指定的构造器。须在定义子类构造器时带上override
修饰符。即使你重写系统提供的默认构造器也需要带上override
修饰符。
无论是重写属性,方法或者是subscript,只要含有override
修饰符就会去检查父类是否有相匹配的重写指定构造器和验证重写构造器参数。
注意:当你重写一个父类指定构造器时,你总是需要写override
修饰符,即使你的子类构造器的实现是一个便利构造器。
相反地,如果你写了一个和父类便利构造器相匹配的子类构造器,子类都不能直接调用父类的便利构造器,因此,严格来说,子类没有复写父类的便利构造器,因此,这里不必加override修饰符。
下面是一个基类:
1 class Vehicle { 2 var numberOfWheels = 0 3 var description: String { 4 return "\(numberOfWheels) wheel(s)" 5 } 6 }
Vehicle
类只为存储型属性提供默认值,而不自定义构造器。因此,它会自动生成一个默认构造器,默认构造器通常在类中是指定构造器,它可以用于创建属性叫numberOfWheels
值为0
的Vehicle
实例。
1 let vehicle = Vehicle() 2 print("Vehicle: \(vehicle.description)") 3 // Vehicle: 0 wheel(s)
下面来定义一个子类:
1 class Bicycle: Vehicle { 2 override init() { 3 super.init() 4 numberOfWheels = 2 5 } 6 }
Bicycle类定制了一个构造器init(),这个构造器和父类的指定构造器匹配,因此要加上override关键字。
Bicycle
的构造器init()
一开始调用super.init()
方法,这个方法的作用是调用Bicycle
的父类Vehicle
。这样可以确保Bicycle
在修改属性之前它所继承的属性numberOfWheels
能被Vehicle
类初始化。在调用super.init()
之后,原本的属性numberOfWheels
被赋值为2
。
下面创建一个Bicycle的实例:
1 let bicycle = Bicycle() 2 print("Bicycle: \(bicycle.description)") 3 // Bicycle: 2 wheel(s)
注意:子类可以在初始化时修改继承变量属性,但是不能修改继承过来的常量属性。
(6)自动构造器的继承
如上所述,子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重写父类的构造器,并且在尽可能安全的情况下以最小的代价来继承父类的构造器。
假设要为子类中引入的任意新属性提供默认值,适用以下2个规则:
规则 1
如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
规则 2
如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。
即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
注意:子类可以通过满足规则2的方式,使用子类便利构造器来实现父类的指定构造器。
8、指定构造器和便利构造器的操作
下面展示指定构造器、便利构造器和自动构造器继承步骤。包括了Food
, RecipeIngredient
, and ShoppingListItem三个类的继承层次结构。
基类Food:
1 class Food { 2 var name: String 3 init(name: String) { 4 self.name = name 5 } 6 convenience init() { 7 self.init(name: "[Unnamed]") 8 } 9 }
它的构造器链是:便利构造器init()调用指定构造器init(name:String).
类没有提供一个默认的逐一成员构造器,所以Food
类提供了一个接受单一参数name
的指定构造器。这个构造器可以使用一个特定的名字来创建新的Food
实例:
1 let namedMeat = Food(name: "Bacon") 2 // namedMeat's name is "Bacon"
Food
类中的构造器init(name: String)
被定义为一个指定构造器,因为它能确保所有新Food
实例的中存储型属性都被初始化。Food
类没有父类,所以init(name: String)
构造器不需要调用super.init()
来完成构造。
Food
类同样提供了一个没有参数的便利构造器 init()
。这个init()
构造器为新食物提供了一个默认的占位名字,通过代理调用同一类中定义的指定构造器init(name: String)
并给参数name
传值[Unnamed]
来实现:
1 let mysteryMeat = Food() 2 // mysteryMeat's name is "[Unnamed]"
下面是Food
的子类RecipeIngredient
。RecipeIngredient
类构建了食谱中的一味调味剂。它引入了Int
类型的数量属性quantity
(以及从Food
继承过来的name
属性),并且定义了两个构造器来创建RecipeIngredient
实例:
1 class RecipeIngredient: Food { 2 var quantity: Int 3 init(name: String, quantity: Int) { 4 self.quantity = quantity 5 super.init(name: name) 6 } 7 override convenience init(name: String) { 8 self.init(name: name, quantity: 1) 9 } 10 }
RecipeIngredient
类拥有一个指定构造器init(name: String, quantity: Int)
,它可以用来产生新RecipeIngredient
实例的所有属性值。这个构造器一开始先将传入的quantity
参数赋值给quantity
属性,这个属性也是唯一在RecipeIngredient
中新引入的属性。随后,构造器将任务向上代理给父类Food
的init(name: String)
。这个过程满足两段式构造过程中的安全检查1。
RecipeIngredient
也定义了一个便利构造器init(name: String)
,它只通过name
来创建RecipeIngredient
的实例。这个便利构造器假设任意RecipeIngredient
实例的quantity
为1,所以不需要显示指明数量即可创建出实例。这个便利构造器的定义可以让创建实例更加方便和快捷,并且避免了使用重复的代码来创建多个quantity
为 1 的RecipeIngredient
实例。这个便利构造器只是简单的将任务代理给了同一类里提供的指定构造器。
注意,RecipeIngredient
的便利构造器init(name: String)
使用了跟Food
中指定构造器init(name: String)
相同的参数。因为这个便利构造器重写要父类的指定构造器init(name: String)
,必须在前面使用override
关键字。
在这个例子中,RecipeIngredient
的父类是Food
,它有一个便利构造器init()
。这个构造器因此也被RecipeIngredient
继承。这个继承的init()
函数版本跟Food
提供的版本是一样的,除了它是将任务代理给RecipeIngredient
版本的init(name: String)
而不是Food
提供的版本。
所有的这三种构造器都可以用来创建新的RecipeIngredient
实例:
1 let oneMysteryItem = RecipeIngredient() 2 let oneBacon = RecipeIngredient(name: "Bacon") 3 let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
下面是RecipeIngredient
的子类:ShoppingListItem
。
购物单中的每一项总是从unpurchased
未购买状态开始的。为了展现这一事实,ShoppingListItem
引入了一个布尔类型的属性purchased
,它的默认值是false
。ShoppingListItem
还添加了一个计算型属性description
,它提供了关于ShoppingListItem
实例的一些文字描述:
1 class ShoppingListItem: RecipeIngredient { 2 var purchased = false 3 var description: String { 4 var output = "\(quantity) x \(name)" 5 output += purchased ? " ✔" : " ✘" 6 return output 7 } 8 }
注意:ShoppingListItem
没有定义构造器来为purchased
提供初始化值,这是因为任何添加到购物单的项的初始状态总是未购买。
由于它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,ShoppingListItem
将自动继承父类中所有的指定构造器和便利构造器。
1 var breakfastList = [ 2 ShoppingListItem(), 3 ShoppingListItem(name: "Bacon"), 4 ShoppingListItem(name: "Eggs", quantity: 6), 5 ] 6 breakfastList[0].name = "Orange juice" 7 breakfastList[0].purchased = true 8 for item in breakfastList { 9 print(item.description) 10 } 11 // 1 x Orange juice ✔ 12 // 1 x Bacon ✘ 13 // 6 x Eggs ✘
9、可失败的构造器
如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器是非常有用的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init
关键字后面加添问号(init?)
。
注意: 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其类型相同
可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。你通过return nil
语句,来表明可失败构造器在何种情况下“失败”。
注意: 严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了能确保对象自身能被正确构建。所以即使你在表明可失败构造器,失败的这种情况下,用到了return nil
,也不要在表明成功的这种情况下,使用关键字 return。
1 struct Animal { 2 let species: String 3 init?(species: String) { 4 if species.isEmpty { return nil } 5 self.species = species 6 } 7 }
该结构体还定义了一个,带一个String
类型参数species
的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。
可以通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功:
1 let someCreature = Animal(species: "Giraffe") 2 // someCreature is of type Animal?, not Animal 3 4 if let giraffe = someCreature { 5 print("An animal was initialized with a species of \(giraffe.species)") 6 } 7 // prints "An animal was initialized with a species of Giraffe"
如果给该可失败构造器传入一个空字符串作为其参数,则该可失败构造器失败。
1 let anonymousCreature = Animal(species: "") 2 // anonymousCreature is of type Animal?, not Animal 3 4 if anonymousCreature == nil { 5 print("The anonymous creature could not be initialized") 6 } 7 // prints "The anonymous creature could not be initialized"
注意:空字符串(""
)和一个值为nil
的可选类型的字符串是两个完全不同的概念。上例中的空字符串(""
)其实是一个有效的,非可选类型的字符串。对于Animal
这个类的species
属性来说,它更适合有一个具体的值,而不是空字符串。因此,用空字符串来断定构造失败。
(1)枚举类型的可失败构造器
可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。参数不满足你所期望的条件时,构造失败。
1 enum TemperatureUnit { 2 case Kelvin, Celsius, Fahrenheit 3 init?(symbol: Character) { 4 switch symbol { 5 case "K": 6 self = .Kelvin 7 case "C": 8 self = .Celsius 9 case "F": 10 self = .Fahrenheit 11 default: 12 return nil 13 } 14 } 15 }
你可以通过给该可失败构造器传递合适的参数来获取这三个枚举成员中相匹配的其中一个枚举成员。如果不匹配,则构造失败:
1 let fahrenheitUnit = TemperatureUnit(symbol: "F") 2 if fahrenheitUnit != nil { 3 print("This is a defined temperature unit, so initialization succeeded.") 4 } 5 // prints "This is a defined temperature unit, so initialization succeeded." 6 7 let unknownUnit = TemperatureUnit(symbol: "X") 8 if unknownUnit == nil { 9 print("This is not a defined temperature unit, so initialization failed.") 10 } 11 // prints "This is not a defined temperature unit, so initialization failed."
(2)带原始值的枚举类型的可失败构造器
带原始值的枚举类型会自带一个可失败构造器init?(rawValue:)
,该可失败构造器有一个名为rawValue
的默认参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和枚举类型成员所带的原始值匹配,则该构造器构造一个带此原始值的枚举成员,否则构造失败。
1 enum TemperatureUnit: Character { 2 case Kelvin = "K", Celsius = "C", Fahrenheit = "F" 3 } 4 5 let fahrenheitUnit = TemperatureUnit(rawValue: "F") 6 if fahrenheitUnit != nil { 7 print("This is a defined temperature unit, so initialization succeeded.") 8 } 9 // prints "This is a defined temperature unit, so initialization succeeded." 10 11 let unknownUnit = TemperatureUnit(rawValue: "X") 12 if unknownUnit == nil { 13 print("This is not a defined temperature unit, so initialization failed.") 14 } 15 // prints "This is not a defined temperature unit, so initialization failed."
(3)类的可失败构造器
值类型(结构体或枚举类型)的可失败构造器,可以在构造器的实现内任意位置触发构造失败。比如在上述Animal的例子中,结构体的可失败构造器一开始就触发失败,发生在species
属性的值被初始化以前。
而对类而言,类的可失败构造器只能在所有的存储属性被初始化后并且构造器代理调用已经发生后触发失败行为。
1 class Product { 2 let name: String! 3 init?(name: String) { 4 self.name = name 5 if name.isEmpty { return nil } 6 } 7 }
上面定义了一个名为Product
的类。由于该属性的值不能为空字符串,所以我们加入了可失败构造器来确保该类满足上述条件。
但由于Product
类不是一个结构体,所以当想要在该类中添加可失败构造器触发失败条件时,必须确保name
属性被初始化。因此我们把name
属性的String
类型改为隐式解析可选类型(String!
),所有可选类型都有一个默认的初始值nil,意味着该类的所有属性都有了有效的初始值
。因此Product类的可失败构造器可以在一开始就触发失败行为。
name属性是一个常量属性,因此,一旦构造成功,name的值一定非空。因为定义成隐式解析可选类型,所以使用时不必检查是否非空:
1 if let bowTie = Product(name: "bow tie") { 2 // no need to check if bowTie.name == nil 3 print("The product's name is \(bowTie.name)") 4 } 5 // prints "The product's name is bow tie"
(4)构造失败的传递
可失败构造器同样满足在构造器链中所描述的构造规则。其允许在同一类,结构体和枚举中横向代理其他的可失败构造器。类似的,子类的可失败构造器也能向上代理基类的可失败构造器。
无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。
注意: 可失败构造器也可以代理调用其它的非可失败构造器。通过这个方法,你可以为已有的构造过程加入构造失败的条件。
1 class CartItem: Product { 2 let quantity: Int! 3 init?(name: String, quantity: Int) { 4 self.quantity = quantity 5 super.init(name: name) 6 if quantity < 1 { return nil } 7 } 8 }
CartItem的可失败构造器开始先向上代理调用父类的构造器 init(name:)
。这满足了可失败构造器总是在触发构造失败这个行为前执行构造代理调用这个条件。
如果由于name
的值为空而导致父类的构造器在构造过程中失败。则整个CartIem
类的构造过程都将失败,后面的子类的构造过程都将不会被执行。如果父类构建成功,则继续运行子类的构造器代码。
如果你构造了一个CartItem
对象,并且该对象的name
属性不为空以及quantity
属性为1或者更多,则构造成功:
1 if let twoSocks = CartItem(name: "sock", quantity: 2) { 2 print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)") 3 } 4 // prints "Item: sock, quantity: 2"
如果你构造一个CartItem
对象,其quantity
的值0
, 则触发构造失败的行为:
1 if let zeroShirts = CartItem(name: "shirt", quantity: 0) { 2 print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)") 3 } else { 4 print("Unable to initialize zero shirts") 5 } 6 // prints "Unable to initialize zero shirts"
类似的, 如果你构造一个CartItem
对象,但name
的值为空, 则基类Product
的可失败构造器将触发构造失败的行为,整个CartItem
的构造行为同样为失败:
1 if let oneUnnamed = CartItem(name: "", quantity: 1) { 2 print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)") 3 } else { 4 print("Unable to initialize one unnamed product") 5 } 6 // prints "Unable to initialize one unnamed product"
(5)重写可失败构造器
可以用子类的可失败构造器重写基类的可失败构造器。也可以用子类的非可失败构造器重写一个基类的可失败构造器。这样做的好处是,即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。
需要注意的是,如果你在子类中用一个非可失败的构造器重写了父类的可失败构造器,唯一可以代理调用父类的可失败构造器的方式是强制解析可失败构造器的返回结果。
注意: 你可以用一个非可失败构造器重写一个可失败构造器,但反过来却行不通。
下例定义了一个名为Document
的类,这个类中的name
属性允许为nil
和一个非空字符串,但不能是一个空字符串:
1 class Document { 2 var name: String? 3 // this initializer creates a document with a nil name value 4 init() {} 5 // this initializer creates a document with a non-empty name value 6 init?(name: String) { 7 self.name = name 8 if name.isEmpty { return nil } 9 } 10 }
下面这个例子,定义了一个Document
类的子类AutomaticallyNamedDocument。这个子类重写了基类的两个指定构造器。确保了不论在何种情况下name
属性总是有一个非空字符串的值。
1 class AutomaticallyNamedDocument: Document { 2 override init() { 3 super.init() 4 self.name = "[Untitled]" 5 } 6 override init(name: String) { 7 super.init() 8 if name.isEmpty { 9 self.name = "[Untitled]" 10 } else { 11 self.name = name 12 } 13 } 14 }
在子类的非可失败构造器的实现中,可以强制解析构造器,调用父类的可失败构造器。
1 class UntitledDocument: Document { 2 override init() { 3 super.init(name: "[Untitled]")! 4 } 5 }
这里,如果super.init的参数name是空字符串,会导致运行时错误。
(6)init!可失败构造器
通常来说,我们通过在init
关键字后添加问号的方式来定义一个可失败构造器,但你也可以使用通过在init
后面添加惊叹号的方式来定义一个可失败构造器(init!)
,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。
你可以在 init?
构造器中代理调用 init!
构造器,反之亦然。 你也可以用 init?
重写 init!
,反之亦然。 你还可以用 init
代理调用init!
,但这会触发一个断言:是否 init!
构造器会触发构造失败?
10、必要构造器
在类的构造器前添加 required
修饰符表明所有该类的子类都必须实现该构造器:
1 class SomeClass { 2 required init() { 3 // initializer implementation goes here 4 } 5 }
当子类重写基类的必要构造器时,必须在子类的构造器前同样添加required
修饰符以确保当其它类继承该子类时,该构造器同为必要构造器。在重写基类的必要构造器时,不需要添加override
修饰符:
1 class SomeSubclass: SomeClass { 2 required init() { 3 // subclass implementation of the required initializer goes here 4 } 5 }
注意:如果子类继承的构造器能满足必要构造器的需求,则你无需显式地在子类中提供必要构造器的实现。
11、用函数或闭包来设置默认属性值
如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。
下面是闭包产生属性默认值的框架:
1 class SomeClass { 2 let someProperty: SomeType = { 3 // create a default value for someProperty inside this closure 4 // someValue must be of the same type as SomeType 5 return someValue 6 }() 7 }
注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
注意:如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的self
属性,或者调用其它的实例方法。
1 struct Checkerboard { 2 let boardColors: [Bool] = { 3 var temporaryBoard = [Bool]() 4 var isBlack = false 5 for i in 1...10 { 6 for j in 1...10 { 7 temporaryBoard.append(isBlack) 8 isBlack = !isBlack 9 } 10 isBlack = !isBlack 11 } 12 return temporaryBoard 13 }() 14 func squareIsBlackAtRow(row: Int, column: Int) -> Bool { 15 return boardColors[(row * 10) + column] 16 } 17 }
每当一个新的Checkerboard
实例创建时,对应的赋值闭包会执行,一系列值会被计算出来作为默认值赋值给boardColors
,并可以通squareIsBlackAtRow
这个工具函数来查询。
1 let board = Checkerboard() 2 print(board.squareIsBlackAtRow(0, column: 1)) 3 // prints "true" 4 print(board.squareIsBlackAtRow(9, column: 9)) 5 // prints "false"