14.构造函数
构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。
与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
1.存储属性的初始赋值
- 类和结构体在创建实例时,必须为所有非
lazy
非可选
类型存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。 - 可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
- 构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。
struct InitTest { var iTemp = 3; //默认值赋值 var strTemp: String; //默认是没有值的 var arr: [Int]?; //可选类型的变量会有默认值nil //构造器赋值 init() { strTemp = "Hello"; //必须赋初值 } }
2.自定义构造过程
- 自定义构造过程时,可以在定义中提供构造参数,指定所需值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。
struct Celsius { let name: String; //常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。 var temperatureInCelsius: Double; var description: String?; //可选类型的属性将自动初始化为nil //外部名和内部名一样的构造器参数 init(fahrenheit: Double) { name = "Hi"; temperatureInCelsius = (fahrenheit - 32.0) / 1.8; } //带外部名和内部名的构造器参数 init(fromKelvin kelvin: Double) { name = "Hi"; temperatureInCelsius = kelvin - 273.15; } //不带外部名的构造器参数 init(_ celsius: Double) { name = "Hi"; temperatureInCelsius = celsius; } } //let bodyTemperature = Celsius(name: "Hi", temperatureInCelsius: 32, description: "Hello World"); //错误:如果自定义了构造器, 那么默认构造器会丢失;如果是结构体,还将丢失逐一成员构造器 let bodyTemperature1 = Celsius(fahrenheit: 100); //bodyTemperature1.name = "Hello"; //错误:常量属性被赋值后将永远不可更改。 let bodyTemperature2 = Celsius(fromKelvin: 300); let bodyTemperature3 = Celsius(37.0);
3.默认构造器
- 如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器。这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。
- 如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。
struct Size { var width = 0.0; var height = 0.0; } let zeroByZero = Size(); //默认构造器 let twoByTwo = Size(width: 2.0, height: 2.0); //逐一成员构造器
4.值类型的构造器代理
- 构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
- 构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考继承),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
struct Size { var width = 0.0; var height = 0.0; } struct Point { var x = 0.0; var 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 centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0));
5.类的继承和构造过程
- 指定构造器(designated initializers)是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。
- 便利构造器(convenience initializers)是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。
- 语法结构
//指定构造器 init(parameters) { statements } //便利构造器 convenience init(parameters) { statements }
- 类的构造器代理规则
- 指定构造器必须调用其直接父类的的指定构造器。
- 便利构造器必须调用同一类中定义的其它构造器。
- 便利构造器必须最终导致一个指定构造器被调用。
如下图所示:
- 构造器的继承和重写
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误地用来创建子类的实例。
子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用:
- 如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
- 如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。
//示例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: 3) } //重写父类的便利构造器时,不需要加override关键字 convenience init() { self.init(name: "Unnamed", quantity: 3); } } let oneMysteryItem = RecipeIngredient(); let oneBacon = RecipeIngredient(name: "Bacon"); let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6); //示例1说明:RecipeIngredient提供了父类的所有指定构造器的实现。因此,RecipeIngredient会自动继承父类的所有便利构造器。 //示例2 class ShoppingListItem: RecipeIngredient { var purchased = false; var description: String { var output = "\(quantity) x \(name)"; output += purchased ? " ✔" : " ✘"; return output; } } let item1 = ShoppingListItem(); let item2 = ShoppingListItem(name: "Bacon"); let item3 = ShoppingListItem(name: "Eggs", quantity: 6); //示例2说明:ShoppingListItem为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,ShoppingListItem将自动继承所有父类中的指定构造器和便利构造器。
6.可失败构造器
- 如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。其语法为在init关键字后面添加问号(init?)。通过return nil语句来表明可失败构造器在何种情况下应该“失败”。
//示例1:带可失败构造器的结构体 struct Animal { let species: String; //可失败构造器 init?(species: String) { if species.isEmpty { return nil; } self.species = species; } } let animal = Animal(species: ""); //nil //示例2:带可失败构造器的枚举类型 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 temperatureUnit = TemperatureUnit(symbol: "H"); //nil //示例3:带原始值的枚举类型的可失败构造器(等效于上面的TemperatureUnit) //说明:带原始值的枚举类型会自带一个可失败构造器init?(rawValue:) enum TemperatureUnit2: Character { case Kelvin = "K", Celsius = "C", Fahrenheit = "F"; } let fahrenheitUnit = TemperatureUnit2(rawValue: "H");
【说明】:也可以通过在init
后面添加惊叹号的方式来定义一个可失败构造器(init!
)。不过,一旦init!
构造失败,则会触发一个断言。
7.必要构造器:在类的构造器前添加required修饰符表明所有该类的子类都必须实现该构造器
- 在子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加override修饰符
- 如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。
class SomeClass { required init() { } } class SomeSubclass: SomeClass { required init() { } }
无善无恶心之体,
有善有恶意之动,
知善知恶是良知,
为善去恶是格物。