构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。
通过定义构造器来实现构造过程,这些构造器可以看做是用来创建特定类型新实例的特殊方法。与oc中的构造器不同,swift的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类的实例也可以通过定义析构器在实例释放之前执行特定的清除工作。
存储属性的初始赋值
类和结构体在创建实例时,必须为所有存储属性设置合适的初始值。存储型属性的值不能处于一个未知的状态
可以在构造器中为存储型属性赋初值,可以在定义属性使为其设置默认值。
构造器
构造器在创建某个特定类型的新实例是被调用。
struct Fahrenheit {
var temperature:Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("\(f.temperature)")
默认属性值
如前所述,可以在构造器中为存储型属性设置初始值。同样,可以在属性声明时为其设置默认值。
struct Fahrenheit {
var temperature:Double = 32.0
}
自定义构造过程
构造参数
自定义构造过程时,可以在定义中提供构造参数,指定所需值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。
下面例子中定义了一个包含摄氏度的结构体Celsius。它定义了两个不同的构造器:
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)
let freezingPointOfWater = Celsius(fromKelvin:2713.15)
参数的内部名称和外部名称
跟函数和方法参数相同,构造参数也拥有一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。因此在调用构造器时,主要通过构造器中的参数名和类型来确定应该被调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,swift会为构造器的每个参数自动生成一个跟内部名字相同的外部名。
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)
如果不通过外部参数名字传值,没有办法调用这个构造器。只要构造器定义了某个外部参数名,必须使用它。
不带外部名的构造器参数
如果不希望为构造器的某个参数提供外部名字,可以使用下划线来显示描述它的外部名,以此重写上面所说的默认行为。
可选属性类型
如果你定制的类型包含一个逻辑上允许取值为空的存储类型属性——无论是因为它无法再初始化时赋值,还是因为它在之后某个时间点可以赋值为空——都需要将它定义为可选类型。可选类型的属性将自动初始化为nil,表示这个属性是有意在初始化时设置为空的。
class SurveyQuestion {
var text:String
var response:String?
init(text:String) {
self.text = text
}
func ask() {
print(text)
}
}
调查问题的答案在回答前是无法确定的,因此我们将属性response声明为String?类型,或者说是可选字符串类型。当SurveyQuestion实例化时,将自动赋值为nil,表明此字符串暂时还没有值。
构造过程中常量属性的修改
可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。
默认构造器
如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么swift会给这些结构体或类提供一个默认构造器。这个默认的构造器将简单的创建一个所有属性值都设置为默认的实例。
class ShoppingListItem {
var name:String?
var quantity = 1
var purchased = false
}
由于ShoppingListItem类中的所有属性都有默认值,它是没有父类的基类,将自动获取一个可以为所有属性设置默认值的默认构造器。
类的继承和构造过程
类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。
swift为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,他们分别是指定构造器和便利构造器。
指定构造器和便利构造器
指定构造器时类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
每一个类都必须拥有至少一个指定的构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。
便利构造器时类中比较次要的、辅助型构造器。可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定的构造器,能够节省更多时间并让类的构造过程更清晰明了。
指定构造器和便利构造器的语法
指定构造器:
init(<#parameters#>) {
<#statements#>
}
便利构造器:
convenience init(<#parameters#>) {
<#statements#>
}
类的构造器代理规则
为了简化指定构造器和便利构造器之间的调用关系,swift采用以下三条规则来限制构造器之间的代理调用:
1、指定的构造器必须调用其直接父类的指定构造器
2、便利构造器必须调用同类中定义的其它构造器
3、便利构造器必须最终导致一个指定构造器被调用。
构造器的继承和重写
跟oc中的子类不同,swift中的子类默认情况下不会继承父类的构造器。swift的这样的机制可以防止一个父类的简单构造器被一个更精细 子类继承,并被错误的用来创建子类的实例。
假如希望自定义的子类中能提供一个或多个跟父类相同的构造器,可以在子类中提供这些构造器的自定义实现。
当在编写一个和父类中指定构造器相匹配的子类构造器时,实际上是在重写父类的这个指定构造器。因此,必须在定义子类构造器时带上override修饰符。即使你重写的系统自动提供的默认构造器,也需要带上override修饰符。
正如重写属性,方法或者下标,override修饰符会让编译器去检查父类中是否有相匹配指定的构造器,并验证构造器参数是否正确。
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器,因此,严格意义上来讲,子类并未对一个父类的构造器提供重写,最后的结果是,子类中重写一个父类便利构造器时,不需要override前缀。
class Vehicle{
var numberOfWheels = 0
var description:String {
return "\(numberOfWheels)"
}
}
class Bicycle: Vehicle {
override init(){
super.init()
numberOfWheels = 2
}
}
子类Bicycle定义了一个自定义指定构造器。这个指定的构造器和父类的指定构造器相匹配,所以带有override。
构造器的自动继承
子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类的构造器可以被自动继承的。在实践中,这意味着对于许多常见的场景不必重写父类的构造器,并且可以安全的情况下以最小的代价继承父类的构造器。
建设你为子类引入的所有新属性都提供了默认值,以下2个规则适用:
1.如果子类没有定义任何指定的构造器,它将自动继承所有父类的指定构造器。
2.如果子类提供了所有父类指定构造器的实现——无论是通过1继承过来的,还是提供自定义实现——它将自动继承所有父类的便利构造器。
指定构造器和便利构造器实践
接下来的例子将实践中展示指定构造器、便利构造器以及构造器的自动继承。
class Food { var name:String init(name:String) { self.name = name } convenience init(){ self.init(name:"[Unnamed]") }
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) } }
由于它为自己引入的所有属性都提供了默认值,并自己没有定义任何构造器,将自动继承所有父类中的指定构造器和便利构造器。class ShoppingListItem: RecipeIngredient { var purchased = false var description:String { var output = "\(quantity) x \(name)" output += purchased ? " ?" : " ?" return output } }
可失败构造器
如果一个雷、结构体或者枚举类型的对象,在构造过程中可能失败,则为其定义一个可失败构造器。这里所指的“失败”是指,给构造器传入无效的参数,或缺少某种所需的外部资源,又或者不满足某种必要的条件等。为了妥善处理这种构造过程中可能会失败的情况。可以在一个雷,结构体或是枚举类型的定义中,添加一个或多个可失败构造器,其语法为init关键字后面添加问号init?。可失败构造器会创建一个类型为自身类型的可选类型的对象,通过return nil语句来表明可失败构造器在何种情况下应该“失败”。可以通过该可失败的构造器来构建一个Animal的实例,并检查构造过程是否成功:struct Animal { let species:String init?(species:String) { if species.isEmpty { return nil } self.species = species } }
如果在这里传入的是空字符串就会导致构造失败,返回的就是nil。let someCreature = Animal(species:"Giraffe") if let giraffe = someCreature { print("\(giraffe.species)") }
枚举类型的可失败构造器
可以通过一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的参数无法匹配任何枚举成员,则构造失败。可以利用该可失败构造器在三个枚举成员中获取一个相匹配的枚举成员,当参数的值不能与任何成员相匹配,则构造失败。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 } } }
带原始值的枚举类型的可失败构造器
带原始值的枚举类型会自带一个可失败构造器init?(rawValue:),该可失败构造器有一个名为rawValue的参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和某个枚举成员的原始值匹配,则构造器会构造相应的枚举成员,否则构造失败。上面的例子可以重写为:enum TemperatureUnit:Character { case Kelvin = "K",Celsius = "C",Fahrenheit = "F" }
构造失败的传递
类,结构体,枚举的可失败构造器可以横向代理到类型中的其他可失败的构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。无论是向上代理还是横向代理,如果代理奥的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。CartItem可失败构造器首先验证接受的quantity值是否大于等于1,如果quantity值无效,则立即终止整个构造过程,返回失败结果,且不再执行余下代码,同样,Product的可失败构造器首先检查name值,假如name值为空字符串,则构造器立即执行失败。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) } }
重写一个可失败构造器
如同其他的构造器,可以在子类中重写父类的可失败构造器。或者你可可以用子类的非可失败构造器重写一个父类的可失败构造器,可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。注意,当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理奥父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。下面的子类重写了父类的两个指定构造器,确保了无论是使用init()构造器,还是init(name:)构造器并为参数传递空字符串,生成的实例中的name属性总有初始值“[Untitled]”:class Document { var name:String? init() { } init?(name:String) { self.name = name if name.isEmpty { return nil } } }
子类用一个可失败构造器重写了父类的可失败构造器。因为子类用另一种方式处理了空字符串的情况,所以不再需要一个可失败的构造器,因此子类用一个非可失败的构造器代替了父类的可失败的构造器。class AutomaticallyNameDocument: Document { override init(){ super.init() self.name = "[Untitled]" } override init(name:String){ super.init() if name.isEmpty { self.name = "[Untitled]" }else{ self.name = name } } }
可失败构造器init!
通常来说我们通过在init关键字后添加问号的方式来定义一个可失败构造器,但你也可以通过在init后面添加惊叹号的方式来定义一个可失败的构造器,该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。可以在init?中代理到init!反之亦然,可以用init?重新init!,可以用init代理到init!,不过,一旦init!构造失败,则会触发一个断言。必要构造器
在类的构造器前添加required修饰符表明所有该类的子类都必须实现该构造器,在子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要override修饰符。
选自《the swift programming language》中文版