1.设置存储属性的初始值
在创建类或结构实例时,类和结构必须为所有存储属性设置初始值。存储属性不能保留在不确定的状态。
可以在构造器中设置存储属性的初始值,也可以在属性声明时设置默认值。
注意:
将默认值分配给存储属性或在初始化程序中设置其初始值时,将直接设置该属性的值,而不调用任何属性观察者。
(1).构造器
可以调用构造器创建特定类型的新实例。无参构造器是一种最简单的初始化方法,使用init关键字:
init() {
// perform some initialization here
}
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"
(2).默认的属性值
可以在构造器中设置存储属性的初始值,如上所示。或者,将默认属性值指定为属性声明的一部分。通过在定义属性时为其指定初始值,可以指定默认属性值。
struct Fahrenheit {
var temperature = 32.0
}
2.自定义初始化
可以使用输入参数和可选属性类型自定义初始化过程,或者在初始化期间分配常量属性,如以下部分所述。
(1).初始化参数
可以提供初始化参数作为构造器定义的一部分,来定义在自定义初始化过程中值的类型和名称。初始化参数与函数和方法参数具有相同的功能和语法。
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
(2).参数名称和参数标签
与函数和方法参数一样,初始化参数既可以具有在构造器体内使用的参数名称,也可以具有在调用构造器时使用的参数标签。
构造器参数的名称和类型在确定调用哪个构造器时起着特别重要的作用。因此,若没有给构造器中的每个参数提供参数标签,则Swift会给每个参数提供自动参数标签。
truct 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)
// this reports a compile-time error - argument labels are required
(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)
// bodyTemperature.temperatureInCelsius is 37.0
(4).可选属性类型
可以在自定义的类型中使用可选的存储属性。可选类型的属性将自动初始化为值nil,表示该属性在初始化期间无值。
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."
(5).在初始化过程中分配常量属性
只要在初始化完成时将其设置为确定值,就可以在初始化期间的任何时刻为常量属性赋值。一旦为常量属性赋值,就无法进一步修改它。
注意:
对于类实例,只能在类初始化期间修改常量属性。而且常量属性不能被子类修改。
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.)"
3.默认构造器
- 类的默认构造器
当一个类的所有属性都有默认值,且没有提供任何构造器,则系统提构一个默认的无参构造器。
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
- 结构体的默认构造器
如果结构类型没有定义任何自己的构造器,则它们会自动接收一个全能初始化器。与默认构造器不同,即使结构体的存储属性没有默认值,该结构体也会接收一个全能初始化器。
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
4.值类型的构造器委托
构造器可以调用其他构造器来执行实例初始化的一部分。此过程称为构造器委托,可避免跨多个构造器拷贝代码。
对于值类型和类类型,构造器委托的工作原理和允许的委托形式的规则是不同的。值类型(结构和枚举)不支持继承,因此它们的构造器委托过程相对简单,因为它们只能委托给它们自己提供的另一个构造器。然而,类可以从其他类继承。这意味着类有额外的职责,以确保在初始化期间为它们继承的所有存储属性分配适当的值。
如果为值类型提供了一个自定义构造器,则无法再访问该类型的默认构造器。
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()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
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)
5.类继承和初始化
类的所有存储属性(包括从父类继承的存储属性)都必须在初始化期间赋值。Swift为类类型定义了两种构造器,以确保所有存储属性都能被初始化。这两种构造器是指定构造器
和便利构造器
。
(1).指定构造器和便利构造器
- 指定构造器是类的主要构造器。它可以完全初始化该类引入的所有属性,并调用适当的超类构造器,以继续超类链上的初始化过程。
- 每个类必须至少有一个指定构造器。
- 便利构造器是次要的,支持类的构造器。
(2).指定构造器和便利构造器的语法格式
- 指定构造器
init(parameters) {
statements
}
- 便利构造器
convenience init(parameters) {
statements
}
(3).类类型的构造器委托
为了简化指定构造器和便利构造器之间的关系,为Swift对构造器之间的委托调用应用以下三个规则:
- 规则1 :一个指定构造器必须从其直接超类中调用一个指定构造器
- 规则2:一个便利构造器必须调用同一个类中的另一个构造器。
- 规则3:便利构造器最终必须调用指定构造器。
简单来说: - 指定构造器必须总是向上委托。
- 便利构造器必须总是横向委托。
规则如下图所示:
(4).两阶段初始化
在Swift中,类的初始化分两个阶段:
- 第一阶段:每个存储属性都由引入它的类分配一个初始值。
- 第二阶段:当每个存储属性的初始化状态都被确定之后,开始第二阶段。在新实例被认为可以使用之前,每个类都有机会进一步自定义它的存储属性。
两阶段初始化可以防止每个属性值在初始化之前被访问;并能够防止属性值被另一个构造器意外地设置为其他值。
Swift的编译器执行四个有用的安全检查,以确保完成两阶段初始化而不会出现错误:
-
安全检测一:
指定构造器必须确保其类引入的所有属性在向上委托给超类构造器之前都被初始化。
如上所述,只有当对象的所有存储属性的初始状态已知时,才认为对象的内存是完全初始化的。为了满足这一规则,指定构造器必须确保在传递到继承链上之前对其自身的所有属性进行初始化。 -
安全检测二:
在给继承属性赋值之前,指定构造器必须先调用超类构造器。若没有,指定构造器赋的新值将被超类覆盖,作为其初始化的一部分。 -
安全检测三:
在为任何属性(包括由同一个类定义的属性)赋值之前,便利构造器必须委托给另一个构造器。如果没有,那么便利构造器赋的新值将被其类的指定构造器覆盖。 -
安全检测四:
只有当初始化的第一阶段结束之后, 构造器才能调用实例方法,读取实例属性的值,将self作为值引用。
基于上面的四个安全检测,以下是两阶段初始化的过程:
Phase 1
- 在类中调用指定构造器,或便利构造器;
- 给类的新实例对象分配内存。分配的内存尚未初始化;
- 该类的指定构造器确认由该类引入的所有存储属性都有一个值。现在分配给这些存储属性的内存被初始化;
- 指定构造器交给超类的构造器,以对其自身的存储属性执行相同的任务;
- 这将沿着继承链继续向上,直到继承链顶端;
- 一旦到达继承链顶端,继承链中的最后一个类要确保它所有的存储属性都有值,则实例对象的内存被认为完全初始化。第一阶段完成
Phase 1示例图:
如安全检测一所示,指定构造器确保子类中的所有属性都有值。然后,它调用父类指定构造器继续继承链上的初始化。
父类构造器确保父类中的所有属性都有值。没有需要初始化的父类,因此不需要进一步的委托。
只要超类的所有属性都有一个初始值,它的内存就被认为是完全初始化的,阶段1就完成了。
Phase 2 - 从继承链的顶部向下执行,链中的每个指定构造器都可以进一步选择自定义实例。构造器现在可以访问self,可以修改self的属性,调用它的实例方法,等等。
-
最后,链中的任何便利构造器都可以选择自定义实例,并可以使用self。
Phase 2示例图:
一旦父类指定构造器完成,子类的指定构造器就可以执行额外的自定义操作(尽管它也没必这样做)。
最后,一旦子类的指定构造器完成,最初调用的便利构造器就可以执行额外的自定义操作。
(5).构造器的继承和重写
- 与Objective-C中的子类不同,Swift的子类默认不继承父类构造器。
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
(6).自动构造器继承
如上所述,子类默认情况下不会继承父类构造器。但是,如果满足某些条件,超类构造器将自动被继承。在实践中,这意味着不需要在许多常见场景中重写构造器,只要这样做是安全的,就可以以最小的代价继承父类构造器。
假设为子类中引入的任何新属性都提供了默认值,则适用以下两条规则:
-
规则一
:如果你的子类没有定义任何指定构造器,它会自动继承所有超类的指定构造器。 -
规则二
:如果你的子类提供了其所有超类指定构造器的实现------通过按照规则1继承它们,或者通过提供自定义实现作为其定义的一部分------那么它将自动继承所有超类的便利构造器。
子类可以实现一个超类的指定构造器作为子类的便利构造器,来满足规则2 。
(7).指定和便利构造器的应用
下面的示例显示了指定构造器、方便构造器和自动构造器继承的应用。该示例定义了一个由三个类组成的层次结构,分别是Food、RecipeIngredient和ShoppingListItem,并演示了它们的构造器如何相互作用。
层次结构中的基类称为Food,这是一个简单的类,用来封装Food的名称。Food类引入了一个名为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()
构造器调用Food类的指定构造器。
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
第二个类RecipeIngredient
是Food
的子类。RecipeIngredient
类模拟烹饪配方中的一个成分。它引入了一个名为quantity
的整型属性(除了从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
类的初始化链:
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
类ShoppingListItem
是RecipeIngredient
的子类。ShoppingListItem
类模拟了出现在购物清单中的配方配料。
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
因为ShoppingListItem
类为它引入的所有属性提供了默认值,并且它本身没有定义任何构造器,所以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 ✘
6.可失败构造器
在类,结构体和枚举的初始化过程中,由于传入无效的初始化参数值,或缺少某种所需的外部资源,或阻止初始化成功的其他条件等,都会导致初始化失败。为了处理这些可能失败的初始化条件,通常会定义一个或多个可失败的构造器作为类,结构体或枚举定义的一部分。通过在关键字init
后面添加问好(即init?
)定义可失败构造器。可失败构造器创建一个其初始化类型的可选值,初始化失败时返回nil
。
注意:
不能定义具有相同参数类型和名称的可失败构造器和不可失败构造器。
如下例所示:结构体Animal
含有一个常量字符串属性species
,和一个含有参数 species
的可失败构造器。若参数为空,则会触发初始化失败;否则,设置Animal
属性的值,初始化成功:
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"
(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 {
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."
(2).具有原始值枚举的可失败构造器
具有原始值的枚举自动接收一个可失败构造器init?(rawValue:)
,它接受一个与原始值类型相匹配的参数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."
(3).初始化失败的传播
类,结构体或枚举的可失败构造器可以将其委托给同一类,结构体或枚举的另一个可失败构造器。类似地,子类的可失败构造器可以委托给超类的可失败构造器。
在任何一种情况下,若委托给另一个导致初始化失败的构造器,整个初始化过程立即失败,并且不会进一步执行初始化代码。
注意:
一个可失败构造器也可以委托给一个不可失败的构造器。如果需要向现有的初始化过程添加一个潜在的失败状态,而该初始化过程在其他情况下不会失败,请使用此方法。
如下例所示:
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)
}
}
如果创建CartItem
实例时,使用非空的name
和大于1的quantity
,则初始化成功。
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"
如果创建CartItem
实例时,传入的quantity
等于0,则初始化失败。
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"
类似的,如果创建CartItem
实例时,使用的name
为空字符串,则初始化失败。
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"
(4).重写可失败构造器
可以在子类中覆盖超类可失败构造器,就像任何其他构造器一样。或者,使用子类不可失败构造器覆盖超类可失败构造器。这使你能够定义一个初始化不能失败的子类,即使超类的初始化允许失败。
注意,如果使用不可失败的子类构造器覆盖可失败的超类构造器,委托给超类构造器的唯一方式是强制解包可失败超类构造器的结果。
注意:
可以使用不可失败构造器覆盖可失败构造器,但是不能反过来。
如下例所示:
下面的示例定义了一个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 nonempty name value
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
下例是Document
的子类AutomaticallyNamedDocument
。这个子类重写超类的两个指定构造器。如果该子类的实例初始化时没有name
参数,或者传入一个空字符串到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:)
。因为该子类用一种不同于超类的方式处理了空字符串的情况,它的构造器不会失败,因此提供类不可失败的构造器。
可以在构造器中使用强制解包来调用超类的可失败构造器,作为子类不可失败构造器实现的一部分。如下例,UntitledDocument
子类在初始化时,调用了超类的可失败构造器init(name:)
。
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
在这种情况下,如果超类的init(name:)
构造器在调用时,传入空字符串,则这个强制解包操作会导致运行时错误。但是在子类UntitledDocument
的构造器中,调用它时传入了一个字符串常量,该构造器不会失败,因此在运行时不会导致错误发生。
(5). 可失败构造器init!
我们通常会定义一个可失败构造器,该构造器通过在init
关键字(init?
)后放置问号来创建适当类型的可选实例。或者,我们也可以定义一个可失败构造器,该构造器创建一个隐式解包的可选实例,该实例具有适当的类型。为此,在init
关键字(init!
)后面放置一个感叹号,而不是问号。
你可以从init?
委托给init!
,反之亦然;也可以使用init!
覆盖init?
,反之亦然。 你也可以从init
委托给init !
,但是这么做,如果init!
构造器导致初始化失败,会触发断言操作。
7. Required构造器
在类构造器的定义之前添加required
修饰符,来指示类的每个子类都必须实现该初始化器。
class SomeClass {
required init() {
// initializer implementation goes here
}
}
还要将required
修饰符添加到所需构造器的每个子类实现之前,以表明初始化器需求适用于继承链中的其他子类。当重写一个必须实现的指定构造器时,不用再添加override
修饰符。
class SomeSubclass: SomeClass {
required init() {
// subclass implementation of the required initializer goes here
}
}
注意:
如果可以使用继承的构造器满足需求,则无需显式实现required
构造器。
8.使用闭包或函数设置默认属性值
如果存储属性的默认值需要某些自定义或设置,则可以使用闭包或全局函数为该属性提供自定义的默认值。每当初始化属性所属类型的新实例时,将调用闭包或函数,并将其返回值作为属性的默认值。
这些类型的闭包或函数通常会创建与属性相同类型的临时值,调整该值以表示所需的初始状态,然后返回该临时值作为属性的默认值。
使用闭包来提供默认属性值:
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立即执行闭包。如果省略这些括号,则试图将闭包本身赋值给属性,而不是闭包的返回值。
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]
}
}
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"
注意:
如果使用闭包初始化属性,请记住,在执行闭包时,实例的其余部分尚未初始化。这意味着你不能在闭包中访问任何其他属性值,即使这些属性具有默认值。也不能使用隐式self属性,或调用实例的任何方法。