原文html
初始化时准备使用的类,结构体,枚举的实例的过程。这个过程包括为实例上的每一个存储属性设置一个初始值而且执行一些其余的在新实例准备好使用以前必需的配置和初始化。swift
经过定义initializers实现初始过程,像能够调用来建立一个新的特定类型的实例的特殊方法。不想Objective-C,swift的初始化方法没有返回值。他们第一准则是确保一个类型的新的实例在他们第一次使用以前正确的初始化。数组
类类型的实例能够实现deinitializer,在那个类的实例释放以前执行自定义的清楚。更多关于deinitializers的信息,查看Deinitialization.安全
类和结构体必需在这个类或者结构体的实例建立以前把所有的存储属性设置为合适的初始化值。bash
你能够在属性的定义中在一个初始化方法中或者经过分配一个默认的属性值来给一个存储属性设置一个初始化值。微信
当你给存储属性分配一个默认值的时候,或者在初始化方法中设置初始化值,属性的值时直接设置的,没有调用任何属性的观察者。
调用初始化方法来建立一个特殊类型的实例。在他最简单的形式中,初始化方法像灭有参数的方法实例,用init关键字写:闭包
init() {
// perform some initialization here
}复制代码
下面的例子定义了一个新的名为Fahrenheit的新结构体来存储用Fahrenheit温标表示的温度。Fahrenheit结构体有一个存储属性,temperature,是Double类型:app
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"复制代码
结构体定义了一个简单的初始化方法,init,没有参数,使用32初始化存储属性temperature(在Fahrenheit中水的冰点)。ide
你能够在初始化方法中设置一个存储属性的初始化方法,像上面展现的。另外能够,在属性声明中指定一个默认的属性值。经过在定义它的时候给属性分配一个初始化值来指定一个默认的属性值。函数
若是属性通常使用相同的初始化值,在初始化方法中提供一个默认的值而不是设置一个值。最后的结果是同样的,可是默认值把属性的初始化和他的声明绑的更近了。使初始化更简短,清晰而且使你能够从默认值中推导属性的类型。默认初始值也使你更简单的使用默认初始化方法和初始化继承,在这章节后面描述。
能够把上面Fahrenheit结构体用更简单的形式写,经过在属性声明的时候为这个temperature属性提供一个默认的值:
struct Fahrenheit {
var temperature = 32.0
}复制代码
你能够用输入的参数和可选的属性类型自定义初始化方法,或者在初始化过程当中分配常量属性,像下面章节中描述的。
在初始化定义中提供初始化参数,来定义自定义初始化过程当中值的类型和名称。初始化参数有和函数和方法参数同样的特色和语法。
下面的例子定义了一个名为Celsius的结构体,存储了用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复制代码
第一个初始化方法有一个简单的有fromFahrenheit参数标签的初始化参数和一个名为fahrenheit的参数。第二个初始化方法有一个名为简单的初始化参数哦raomKelvin和一个名为kelvin的参数造成。两个初始化把单个参数转换为对应的Celsius值而且把这个值存储在名为temperatureInCelsius属性中。
像函数和方法的参数,初始化参数能够有在初始化方法体中使用的实参和调用初始化方法时使用的形参标签。
不过,初始化方法在括号前没有和函数与方法同样的明确的函数名称。因此,一个初始化方法的名字和类型在肯定应该调用哪一个初始化方法的时候扮演了一个特别重要的角色。所以,若是你没有提供swift为初始化方法中每个参数提供了自动的形参标签。
下面的例子定义了一个名为color的结构体,有三个名为red,green和blue的常量属性。这些属性存储了一个0.0到1.0之间的值来指示color中red,green和blue的值。
Color为他的red,green和blue成员提供了一个有三个适当的Double类型的参数的初始化方法。Color也用简单的white参数提供给了第二个初始化方法,用来为所有三个color部分提供相同的值。
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 - argument labels are required复制代码
若是你不想在初始化参数使用参数标签,经过underscore代替那个参数的明确的参数标签来重写默认的表示。
这里是从上面Initialization Parameters中Celsius例子的扩展版本,用一个添加的初始化方法从一个已经在Celsius标度中存在的Double值来建立一个新的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值来被调用。
若是你的自定义类型有一个逻辑上容许没有值得存储属性--可能由于它的值能够在初始化时设置,或者由于容许他在后面的时候是“no value”--用一个可选类型声明属性。可选类型的属性用nil自动初始化,指明属性是有意的想在初始化时是“no value yet”的。
下面的例子定义了一个名为SurveyQuestion的类,用一个名为response的可选String属性:
class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(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?类型声明,或者“optional String”。它自动分配一个默认的值nil,意味着“no string yet”,当一个新的SurveyQuestion实例被初始化的时候。
你能够在初始化的任什么时候候给常量属性分配一个值,只要在初始化结束以前设置为一个确切的值。一旦常量属性分配了值,它永远都不能修改了。
对于类实例,一个常量属性能够只能在初始化的时候被引入它的类修改。不能被一个子类修改。
你能够用上面的SurveyQuestion雷子来用一个常量属性而不是变量属性来表示问题的text属性,来指定一旦SurveyQuestion的实例被建立,问题不能修改。即便如今text属性是一个常量,他仍然能够在类的初始化器中设置:
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(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为给他们所有的属性提供了默认值而且本身没有提供初始化器的类或者结构体的提供了默认的初始化器。默认初始化器简单的建立了一个新的所有的属性都设置为他们默认值的实例。
这个例子定义了一个名为ShoppiingLIstItem的类,把一个东西的名字,量,和可能状态放在购物列表中:
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()复制代码
由于ShoppingListItem类型所有属性都有默认值,也由于它是一个没有父类的基础类,ShoppingListItem自动获取一个默认的建立一个所有属性都设置为默认值的新的实例的初始化器实现。(name属性是一个可选的String属性,它自动接受一个默认的值nil,即便这个值没有在代码中写。)上面的例子用初始化器语法使用ShoppingListItem类的默认初始化器来建立一个新的类的实例,写做ShoppingListItem(),而且给这个新的实例分配一个名为Item的变量。
结构体类型若是他们没有定义任何他们自定义的初始化器那么会自动接受一个成员初始化器。不想默认初始化器,结构体接受一个成员初始化器,即便他有一些没有默认值的存储属性。
成员初始化结构起是初始化新结构体实例的成员属性的简写方式。能够把新实例的属性的初始化值用名字传递成员初始化器。
下面的例子定义了一个有两个叫width和height的两个属性的名为Size的结构体。两个属性经过分配一个默认的值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)复制代码
当你调用一个成员初始化器时,你能够忽略有默认值的属性的值。在上面的例子中,Size结构体对他的两个height和width属性有默认的值。你能够忽略一个或者两个属性,初始化器对你忽略的属性使用默认的值--例如:
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// Prints "0.0 2.0"
let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// Prints "0.0 0.0"复制代码
初始化器能够调用其余初始化器来执行一个实例的初始化的一部分。这个过程,成为初始化代理,避免多个初始化器的重复代码。
初始化器代理如何工做的规则,什么形式的代理是容许的,值类型和类类型是不一样的。值类型(structures和enumerations)不支持继承,因此他们的初始化代理过程相对简单,由于他们只能够用他们本身提供的初始化器作代理。类,不管怎样,能够从其余类继承,像在Inheritance中描述的。这意味着那些类有额外的职责来保证他们继承的所有存储属性在初始化的时候能被分配到一个合适的值。这些职责的描述在下面的Class Inheritance and Initialization。
对于值类型,当你写本身的自定义初始化器时使用self.init来指向相同值类型的其余初始化器。你只能在初始化器中调用self.int。
注意若是你为值类型定义了一个自定义的初始化器,你就不能在访问那个类型的默认初始化值(或者成员初始化器,若是是一个结构体)。这个约束防止了一个状况,在更复杂的初始化器中提供的额外的基础配置意外的被默认用自动初始化器的人忽略掉。
若是你但愿你的自定义值类型使用默认初始化器和成员初始化器来初始化,也能用你本身的自定义初始化器,在你的扩展中写自定义初始化器,而不是在类型的原始的实现中。更多的信息,查看 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属性的值,经过提供一个特别的原点和尺寸,或者经过提供一个指定的中心点和尺寸。这些初始化选项用三个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(),功能和若是结构体没有本身的自定义初始化器时接受的默认的的初始化器同样。初始化器有一个空的主体,用空的一对花括号表示。调用这个初始化器返回一个origin和size属性都是用他们属性定义中的默认值Point(x:0.0,y:0.0)和Size(width:0.0,height:0.0)来初始化的Rect实例。
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值的基础上计算一个合适的原点。而后调用(或者代理)带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:)初始化器来讲使用已经存在的提供了那个功能的初始化器更方便。
关于另外一种不用本身定义Init()和Init(origin:size:)初始化器来写这个例子的方式,看 Extensions
设计初始化器是一个类的主要初始化器。一个设计初始化器彻底初始化类的所有属性而且调用合适的父类初始化器来向上父类链继续初始化过程。
类经常有很是少的设计初始化器,对类来讲只有一个使很是同常的状况。设计初始化器发生的“漏斗”点,经过跟着父类链向上继续初始化过程。
每一个类必需有至少一个初始化器。在一些状况下,这个需求经过从父类中继承一个或者两个来保证,以下面Automatic Initializer Inheritance描述的。
遍历初始化器是给类的第二个,支持初始化的初始化器。你能够定义一个遍历初始化器来调用一个和遍历构造器在一个类中的设计初始化器来吧设计构造器的参数设置为默认值。你也能够为了特殊的使用状况或者输入值类型来定义一个遍历构造器来建立一个这个类的实例。
若是你的类不须要他们,你不用提供遍历初始化器。当一个一般的初始化模式的便捷方式能够节约时间或者使类的初始化在目的上更明确的时候建立一个遍历初始化器。
类的设计初始化器和值类型的简单初始化器用同样的方式书写:
init(parameters) {
statements
}复制代码
遍历构造器用同样的风格书写,可是把convenience修饰词放在init关键字前面,用一个空格分隔:
convenience init(parameters) {
statements
}复制代码
为了简化设计和便利初始化器之间的关系,swift在初始化器之间为代理调用用了下面三个原则:
原则1
设计初始化器必需从他的直接父类中调用一个设计初始化器。
原则2
遍历初始化器必需从相同的类中调用另外一个初始化器。
原则3
遍历初始化器必需最终调用一个设计初始化器。
一个简单的记住这些的方式:
这些原则在下面的图形中解释:
这里,父类有一个单一的设计初始化器和两个遍历初始化器。一个遍历初始化器调用另外一个初始化器,它转而调用单一的设计初始化器。从上面这保证了原则2和3。父类没有更多的父类,因此原则1没有使用。
这个同种的子类有两个设计初始化器,和一个遍历初始化器。遍历初始化器必需调用两个设计初始化器之一,由于它必需调用相同类中的其余遍历器。在上面这确保了2和3.两个设计初始化器必需调用弗雷中的单一的设计初始化器,来确保上面的原则1.
这些原则没有影响你的类的使用者建立每一个类的实例。上面图标的任何初始化器能够用来建立一个彻底初始化的他们所属于的类的实例。原则只影响你如何写类初始化器的实现。
下面面的图标展现了一个四个类的更加复杂的类继承。他解释了在这个继承中的设计初始化器如何为类初始化执行“funnel”点,简化了链中类之间的内部关系:
swift中的类初始化是一个两阶段的过程。在第一个阶段,每一个存储属性被引入它的类分配了一个初始化值。一旦每一个存储属性的初始状态肯定了,第二个阶段开始,在新的实例认为准备使用以前给每一个类自定义它的存储属性的机会。
两阶段初始化过程的应用是初始化很是安全,同时给与了类层中每个类完整的灵活性。两阶段初始化防止属性值在他们初始化以前被访问,防止属性值被另外一个初始化器意外的设置为不一样的值。
swift的两阶段初始化过程和Objective-C中的初始化类似。主要的不一样在阶段1,Objective-C给每一个属性分配了null或者0(例如0或者nil)。swift的初始化流更灵活,他让你能够设置自定义的初始值,能够处理0和nil不是合法的默认值的类型。
swift的编译器提供了四个有用的安全检查来确保两阶段初始化没有错误的完成:
安全检查1
一个设计的初始化器必需肯定在它代理到父类的初始化器以前所有由本身引入的属性都被初始 化。
像上面提到的,一个对象的内存只用当它的所有存储属性的状态都知道的时候才认为是彻底初始化了。为了能够知足这个原则,设计初始化器必需确保他本身所有的属性在向链上传递以前初始化完了。
安全检查2
一个设计初始化器必需在给他继承的属性分配值以前代理到它的父类初始化器中。若是没有这样,设计初始化器分配的新值将会被父类的初始化器重写。
安全检查3
遍历初始化器必定要在给任何属性分配值以前代理到其余初始化器中(包括同一个类中定义的属性)。若是不这样,遍历初始化器分配的新值会被他的类的设计遍历器重写。
安全检查4
一个初始化器不能调用任何任何实例方法,读取任何实例的属性,或者把self引用为一个值,知道初始化器的第一个阶段完成。
类的实例在第一个阶段结束前不是彻底合法的。一旦类实例在第一阶段结束时才当作是有效的时才能访问属性,调用方法。
这里是两阶段初始化如何完成,在上面四个安全检查的基础之上:
阶段1
阶段2
这里是对于一个假设地子类和父类的初始化调用的阶段1看起来什么样:
在这个例子中,初始化在子类中调用一个遍历初始化器开始。这个遍历初始化器不能修改任何属性。他代理到同一个类的一个设计初始化器。
设计初始化器肯定子类所有的属性有一个值,像安全检查1.而后调用一个父类中的设计初始化器来向链上继续初始化。
父类的设计初始化器确保了全部父类的属性有值。没有更多的父类要初始化,因此不须要更多的代理。
只要所有的父类属性有值,它的内存认为彻底初始化了。阶段1结束。
这里是一样的初始化调用的阶段2看起来的样子:
父类的设计初始化器如今有机会自定义实例(即便他不是必需要这样)。
一旦父类的设计初始化器结束,子类的设计初始化器能够执行额外的自定义(一样的,不是同样要这样作)。
最后,一旦子类的设计初始化器结束了,原先调用的遍历初始化器能够执行额外的自定义。
不想Objective-C中的子类,swift的子类不须要默认继承父类的初始化器。swift的方案防止了父类中的简单初始化器被一个更特殊的子类继承而用来建立了一个不完整或者正确的初始化的子类的新的实例的状况,
父类初始化器在明确的环境下继承,可是只有当他是安全的并且适合这样作。更多的信息,查看下面的 Automatic Initializer Inheritance。
若是你想自定义子类来呈现一个或者多个和父类同样的初始化器,你能够在子类中提供一个这些初始化器的自定义的实现。
当你写了一个匹配一个父类的设计初始化器的子类初始化器的时候,你实际上提供了那个设计初始化器的一个重写。因此,你必须在子类的初始化器定义前写override修饰词。即便你重写了自动提供的默认初始化器也要这样作,像Default Initializers中描述的。
像一个重写的属性,方法或者下标,override修饰词的出现提示swift来检查父类有一个匹配的设计初始化器来重写,而且验证你重写的初始化器的参数是按需求同样指定的。
当重写父类的设计初始化器的时候你一般写override修饰词,即便你子类的初始化器的实现是一个遍历初始化器。
相反的,若是你写了一个子类的匹配父类遍历初始化器的初始化器,那个父类的初始化器能够不被你的子类直接调用,像上面Initializer Delegation for Class Types每条原则所表述的。因此,你的子类没有(严格来说)提供父类初始化器的重写。结果就是,当提供一个父类的遍历初始化器的匹配实现时不写override修饰词。
下面的例子定义了一个名为Vehicle的基础类。基础类声明了一个名为numberOfWheels的存储属性,使用一个默认Int值0.numberOfWheels属性被名为description的计算属性用来建立一个String的vehicle的特性的描述:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}复制代码
若是一个子类的初始化器在初始化过程的阶段2没有执行自定义,父类有一个0参数的设计初始化器,你能够在给子类的所有存储属性分配值以后忽略调用super.init()。
这个例子定义了另外一个Vehicle的子类,名为Hoverboard。在它的初始化器中,Hoverboard类只设置它的color属性。替代了明确的调用super.init,初始化器依靠隐式的调用它的父类的初始化器来完成过程。
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() implicitly called here
}
override var description: String {
return "\(super.description) in a beautiful \(color)"
}
}复制代码
一个Hoverboard的实例使用Vehicle初始化器提供的wheels的默认数字。
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver复制代码
子类能够在初始化时修改继承的变量属性,可是不能修改继承的常量属性。
像上面提到的,子类默认不继承父类的初始化器。不过,若是肯定的条件知足父类的初始化器会自动被继承。实际中,这意味着在一般状况下你不须要写初始化器的重写,而且任什么时候候都是安全的来花费很小的努力而继承父类的初始化器。
假设在一个子类中你为拟引入的新的属性提供了默认的值,应用下面的两个原则:
原则1
若是你的子类没有定义设计初始化器,自动继承他父类所有的设计初始化器
原则2
若是你的子类提供了父类设计初始化器的所有实现--即便按原则1的方式继承的他们,或者在他本身的定义中提供了一个自定义的实现--他自动继承父类的所有遍历初始化器。
即便你的子类添加了更多的遍历初始化器这些原则也适用。
子类能够把父类设计初始化器的实现为子类便利初始化器来做为知足原则2的一部分
下面的例子展现了设计初始化器,便利初始化器,和自动初始化器的继承。这个例子定义了一个名为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]的name值代理到Food类的init(name:String)来微信的food提供一个默认参数占位名:
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"复制代码
层级中的第二个类是Food的子类名为RecipeIngredient。类RecipeIngredient模型了作饭食谱中的原料。它引入了一个名为Quantity的Int属性(出去它从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参数,是RecipeIngredient引入的惟一新的属性。作完这个以后,初始化器代理到Food类的init(name:String)。这个过程确保了上面Two-Phase Initialization安全检查1。
RecipeIngredient也定义了一个便利初始化器,init(name:String),只经过name来建立RecipeIngredient实例。这个便利初始化器假设每一个没有明确quantity的RecipeIngredient实例的quantity为1。这个便利初始化器的定义使REcipeIngredient实例更快更方便的来建立,避免在建立多个单quantityRecipeIngredient实例的时候重复代码。这个便利初始化器简单的代理到类的设计初始化器,传了一个值为1的quantity。
RecipeIngredient提供的init(name:String)便利初始化器和Food中init(name:String)设计初始化器采用同样的参数。由于这个便利初始化器重写了它父类中的设计初始化器,必须使用override修饰词标记(像在Initializer Inheritance and Overriding中描述的)。
即便recipleImg像个便利初始化器同样提供init(name:String),可是REcipeIngredient提供父类所有设计初始化器的实现。因此,REcipeIngredient也自动继承父类的所有便利初始化器。
在这个例子中,RecipeIngredient的父类是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类对出如今购物列表中的食谱原料进行建模。
在购物列表中的每一项开始是“unpurchased”。表示这个事实,ShoppingListItem引入一个名为purchase的布尔属性,有一个false的默认值。ShoppingListItem也增长了一个计算属性description,提供了一个ShoppingListItem实例的文本描述:
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}复制代码
ShoppingListItem没有定义一个初始化器来给purchased提供一个初始值,由于购物列表中的项一般在开始的时候都是unpurchased。
由于它为它引入的所有属性提供了默认的值而且没有定义本身的初始化器,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 {
print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘复制代码
这里,由含有三个新的ShoppingListItem实例的字面数组建立了一个新的名为breakfastList的数组。数组的类型推导为[ShoppingListItem]。在数组建立以后,数组开始的ShoppingListItem的名字有[Unnamed]改成Orange juice而且标记为已经purchased。打印数组中每一个对象的description显示他们默认的状态定期望的改变了。
有时候定义一个初始化器可能失败的类,结构体,或者枚举是有用处的。这个失败可能由无效的初始化参数值触发的,必需外部数据的缺失,或者一些其余阻止初始化成功的状况。
要处理可能失败的初始化状况,在类,结构体,或者枚举定义中定义一个或者多个可失败的初始化器。经过在init关键字后面放置一个问号表示一个可能失败的初始化器(init?)。
不能用相同的参数类型和名称来定义一个可能失败的和不可能失败的初始化器
一个可失败的初始化器建立一个可选的他初始化类型的值。在可失败初始化器中写return nil来表示能够被触发初始化失败的地方。
严格来讲,初始化不返回值。准确的说,他们的任务是确保他们本身在初始化结束以前被完整的正确的初始化。即便你写return nil来触发初始化的失败,不用return关键字来指示初始化成功。
例如,失败的初始化为了数值类型转换而实现。来确保数值类型之间的转换保持值的正确,使用init(exactly:)初始化器。若是类型转化不能保持值,初始化器失败。
let wholeNumber: Double = 12345.0
let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// Prints "12345.0 conversion to Int maintains value of 12345"
let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int
if valueChanged == nil {
print("\(pi) conversion to Int does not maintain value")
}
// Prints "3.14159 conversion to Int does not maintain value"复制代码
下面的例子定义了一个名为Animal的结构体,有一个名为species的String常量属性。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 {
print("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 {
print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"复制代码
检查空的字符串值(例如“”而不是“Giraffe”)和检查来指明可选String值的缺失不同。在上面的例子中,空字符串是有效的,非可选String。不过,animal不适合拥有一个和他的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
}
}
}复制代码
可使用可失败的初始化器来为三个可能的状态选择一个合适的枚举case而且若是参数没有匹配三个状态之一的时候使初始化失败:
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("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 {
print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."复制代码
有raw values的枚举自动接收一个可失败的初始化器,init?(rawValue:),接受一个合适的raw-value类型的名为rawValue的参数而且若是发现了一个则选择一个匹配的枚举状况,或者若是没有匹配的值存在触发一个初始化失败。
你能够重写上面离得TemperatureUnit来使用Character类型的raw values而且使用init?(rawValue:)初始化器:
enum TemperatureUnit: Character {
case kelvin = "K", celsius = "C", fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."
let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."复制代码
一个类,结构体或者枚举的可失败的初始化器能够代理到同一个类,结构体或者枚举中的其余可失败的初始化器。类似的,一个子类可失败初始化器能够代理到父类可失败初始化器。
在这些状况中,若是你代理另一个能致使失败的初始化,整个初始化过程立马失败,再也不有初始化代码执行。
一个可失败的初始化器能够代理到不可失败的初始化器。若是你须要给已存在的不会失败的初始化过程增长潜在的失败状态使用这种方式。
下面的例子定义了一个名为CartItem的子类。CartItem类模型化了一个在线购物车中的对象。CartItem引入了存储的常量属性名为quantity而且确保这个属性一般有一个至少为1的值:
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)
}
}复制代码
CaritItem的可失败初始化器开始验证它接受了一个1或者更大的quantity。若是quantity是无效的,整个初始化过程当即失败而且没有更多的初始化代码执行。一样的,Product的可失败的初始化器检查name值,而且若是name是空字符串初始化器过程当即失败。
若是你用一个非空的name和1或者大于1的quantity建立一个CartItem实例,初始化成功:
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"复制代码
若是你尝试用一个为0的quantity值建立一个CartItem实例,CartItem初始化器使初始化失败:
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"复制代码
类似的,若是你尝试用一个空的name值建立一个CartItem实例,父类的Product初始化器致使初始化失败:
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"复制代码
你能够在子类中重写父类的可失败初始化器,就像其余初始化器同样。或者,你能够用子类的不可失败初始化器来重写父类的可失败初始化器。这是你能够为初始化不能失败的子类,即便父类的初始化器是能够失败的。
注意若是你用子类的不可失败初始化器来重写一个但是白的父类初始化器,惟一代理到父类初始化器的方式是对父类可失败的初始化器进行强解。
你能够用不可失败的初始化器重写一个可失败的初始化器可是反过来不行。
下面的例子定义了一个名为Doucument的类。这个类模型化了一个文档,能够用一个非空字符串或者nil的name属性来初始化,可是不能用空字符串:
class Document {
var name: String?
// this initializer creates a document with a nil name value
init() {}
// this initializer creates a document with a nonempty name value
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}复制代码
下面的例子定义了一个名为AutomaticallyNamedDocument的Document的子类。AutomaticallyNamedDocument子类重写了Document的两个设计初始化器。这些重写确保了AutomaticallyNamedDocument实例有一个初始化name值“[Untitled]”,若是实例没有name初始化,或者若是一个空字符串传给了init(name:)初始化器:
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:)初始化器。由于AutomaticallyNamedDocument用和他的父类不一样的方式处理空字符串的状况,它的初始化器不须要失败,因此它提供了一个非失败版本的初始化器代替。
在初始化器中你可使用强解包在子类的非失败初始化器实现中来调用一个父类中的可失败的初始化器。例以下面的UntitledDoucment子类一般命名为“[Untitled]”,而且在初始化时使用父类中的可失败初始化器init(name:)。
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}复制代码
这种状况,若是父类的init(name:)初始化器调用空字符串的name,强解包操做会致使运行时错误。不过,由于他用一个常量字符串调用,能够看到初始化器不会失败,因此没有运行时错误在这个时候发生。
一般经过在init关键字后面放置一个问号定义一个可失败的初始化器来建立一个适当类型的可选实例。或者,能够定义一个建立一个隐式解包了可选适当类型的实例初始化器。经过在init关键字后面替代问号放置一个感叹号实现。
能够从init?代理到init!,反之亦然,能够用init!重写init?,反之亦然。也能够从init代理到init!,若是iinit!初始化器引发了初始化错误会触发一个断言。
在类初始化器前些required修饰词来指定这个类的每一个子类必需实现这个初始化器:
class SomeClass {
required init() {
// initializer implementation goes here
}
}复制代码
在子类的必需初始化器实现以前也必需写required修饰词,来指明初始化器必需性在链中用于更多的子类。当重写一个必需的设计初始化器时不写override修饰词。
若是能用继承的初始化器知足必需性那你能够不用提供必需初始化器的明确实现。
若是一个存储属性的默认值须要一些自定义或者配置,你可使用闭包或者全局函数来给属性提供一个自定义的默认值。无论任什么时候候包含属性的实例的初始化,闭包或者函数被调用,它的返回值做为属性的默认值分配给他。
这种闭包或者函数通常建立一个和属性同样类型的临时值,修改那个值来表示想要的初始状态,而后把临时值返回做为属性的默认值。
这里是闭包如何用来提供默认属性值的一个大概说明:
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属性,后者调用任何实例方法。
下面的例子定义了一个名为Chessboard的结构体,模型了一个国际象棋的板子。国际象棋在8*8的板子上玩,有黑色和白色的方格。
要表示这个游戏板子,Chessboard结构体只有一个名为boardColors的属性,是一个64个布尔值的数组。数组中的true值表示黑色方格,false值表示白色方格。数组中第一个对象表示板子上上左的方格,而且数组的最后一个对象表示板子上右下的方格。
boardColors数组用闭包初始化来设置它的颜色值:
struct Chessboard {
let boardColors: [Bool] = {
var temporaryBoard = [Bool]()
var isBlack = false
for i in 1...8 {
for j in 1...8 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squareIsBlackAt(row: Int, column: Int) -> Bool {
return boardColors[(row * 8) + column]
}
}复制代码
任何建立新的Chessboard实例的时候,闭包都被执行,boardColors的默认值被计算并返回。上面例子的闭包为板子上每一个方格计算并把合适的颜色放到了名为temporaryBoard的临时数组中,而后一旦他的设置完成做为闭包的返回值返回这个临时数组。返回的数组值存储在boardColors中而且能够用工具函数squareIsBlackAt(row:colum:)查询:
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"复制代码