构造过程
是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。过程中为实例中的每个存储型属性设置初始值和为其执行
必要的准备和初始化任务。
构造过程是通过定义构造器(Initializers)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。
与 Objective-C 中的造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类的实例也可以通过定义析构器(deinitializer)在实例释放之前执行特定的清除工作。
存储型属性的初始赋值
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
注意:
当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器(property observers)。
/*构造器:在创建特定类型的新实例是调用。最简单形式如下
init(){
}
*/
struct Fahrenheit {
//提供默认值
var temperature:Double =10.0
init(){
//初始化赋值如果提供了默认值则这里不必修改其值
temperature =32.0
}
}
var f = Fahrenheit()
print("temperature:\(f.temperature)")//temperature:32.0
/*
自定义构造过程
你可以通过输入参数和可选属性类型来定义构造过程,也可以在构造过程中修改常量属性。
*/
//构造参数
//你可以在定义构造器时提供构造参数,为其提供自定义构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。
struct Celsius {
var temperatureInCelsius:Double =0.0
init(fromFahrenheit fahrenheit:Double) {
temperatureInCelsius = (fahrenheit -32.0) / 1.8
}
init(fromkelvin kelvin:Double) {
temperatureInCelsius = kelvin -273.15
}
}
let boilingPointOfWater =Celsius(fromFahrenheit:212.0)
print(boilingPointOfWater.temperatureInCelsius)//100.0
let freezingPointOfWater =Celsius(fromkelvin:273.15)
print(freezingPointOfWater.temperatureInCelsius)//0.0
/*
参数的内部名称和外部名称
跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来
确定需要调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,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)
/*不带外部名的构造器参数
如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(_)来显示描述它的外部名,以此重写上面所说的默认行为。
*/
struct Celsius1 {
var temperatureInCelsius:Double =0.0
init(_ celsius:Double){
temperatureInCelsius = celsius
}
}
let bodyTemperature =Celsius1(37.0)
print(bodyTemperature.temperatureInCelsius)//37.0
/*只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
注意:
对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
*/
class SurveyQuestion {
//定义常量
let text:String
//定义可选类型(默认值为nil)
var response:String?
init(text:String) {
//初始化修改常量的值
self.text = text
}
func ask() {
print(text)
}
}
/*默认构造器
Swift 将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。
这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。
*/
class ShoppingListItem0 {
var name:String?
var quantity =1
var pruchased =false
}
var item = ShoppingListItem0()
print("item.name:\(item.name) item.quantity: \(item.quantity)")//item.name:nil item.quantity: 1
/*结构体的逐一成员构造器
如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,
通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
*/
struct Size {
var width =0.0, height =0.0
}
let twoByTow =Size(width:2.0, height:2.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()
let originRect =Rect(origin:Point(x:2.0, y:2.0), size:Size(width:5.0, height:5.0))
let centerRect =Rect(center:Point(x:4.0, y:4.0), size:Size(width:3.0, height:3.0))
类的继承和构造过程
类里面的所有存储型属性--包括所有继承自父类的属性--都必须在构造过程中设置初始值。
Swift提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。
指定构造器和便利构造器
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。
便利构造器是类中比较次要的、辅助型的构造器。
指定构造器
init(parameters){
}
//通过关键字convenience来声明便利构造器
convenience init(parameters) {
}
类的构造器代理规则
为了简化指定构造器和便利构造器之间的调用关系,Swift采用以下三条规则来限制构造器之间的代理调用:
规则 1
指定构造器必须调用其直接父类的的指定构造器。
规则 2
便利构造器必须调用同一类中定义的其它构造器。
规则 3
便利构造器必须最终以调用一个指定构造器结束。
一个更方便记忆的方法是:
指定构造器必须总是向上代理
便利构造器必须总是横向代理
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。
/*构造器的继承和重写
Swift子类不会默认继承父类的构造器。重写需要override关键字
注:重写的便利构造器也不能访问父类的便利构造器,子类可以在初始化时修改继承变量属性,但是不能修改继承过来的常量属性。
*/
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)
自动构造器的继承
Swift子类不会默认继承父类的构造器,但是如果特定条件可以满足,父类构造器可以被自动继承。
规则 1
如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
规则 2
如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。
即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
注意:
子类可以通过部分满足规则2的方式,使用子类便利构造器来实现父类的指定构造器。
class Food {
var name:String
init(name:String) {
self.name = name
}
convenience init() {
self.init(name:"[Unnamed]")
}
}
let namedMeat =Food(name:"Bacon")
print(namedMeat.name)//Bacon
let mysteryMeat =Food()
print(mysteryMeat.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)
}
}
//继承父类的便利构造器init() 将任务代理给RecipeIngredient的init(name: String)
let oneMysteryItem =RecipeIngredient()
let oneBacon =RecipeIngredient(name:"Bacon")
let sixEggs =RecipeIngredient(name:"Eggs", quantity: 6)
class ShoppingListItem:RecipeIngredient {
var purchased =false
var description:String {
var output ="\(quantity) x\(name.lowercaseString)"
output +=purchased ?" ✔ " :" ✘ "
return output
}
}
//由于ShoppingListItem为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,
//ShoppingListItem将自动继承所有父类中的指定构造器和便利构造器。
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name:"Bacon"),
ShoppingListItem(name:"Eggs", quantity: 6)
]
breakfastList[0].name ="Orange juice"
breakfastList[0].purchased =true
for item inbreakfastList {
print(item.description)
}
//1 x orange juice ✔
//1 x bacon ✘
//6 x eggs ✘
/*可失败构造器
如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。
“失败”:指参数无效、缺少某种所需外部资源或不满足某种必要条件
语法:init关键字后加问号(init?)在构建对象的过程中,创建一个资深类型为可选类型的对象。通过return nil表示失败。
注:构造器不返回值,除了可失败构造器返回nil外,其他情况不可使用关键字return
*/
struct Animal {
//物种
let species:String
//定义可失败构造器
init?(species:String) {
if species.isEmpty {
returnnil
}
self.species = species
}
}
//创建长颈鹿物种
let someCreature =Animal(species:"Giraffe")
iflet giraffe =someCreature {
print("An animal was initialized width a species of\(giraffe.species)")
//An animal was initialized width a species of Giraffe
}
//如果创建空字符串,可失败构造器失败
let anonymousCreature =Animal(species:"")
if anonymousCreature ==nil {
print("The anonymouse creature cound not be initialized")
//The anonymouse creature cound not be initialized
}
/*枚举类型的可失败构造器
可以用一个可失败构造器选择一个合适的(带一个参数或多个参数)枚举成员,如果提供的参数不满足特定条件可导致构造器失败。
*/
enum TemperatureUint {
case Kelvin, Celsius, Fahrenheit
init?(sysmbol:Character) {
switch sysmbol {
case"K":
self = .Kelvin
case"C":
self = .Celsius
case"F":
self = .Fahrenheit
default:
returnnil
}
}
}
//构造成功
let fahrenheitUint =TemperatureUint(sysmbol:"F")
if fahrenheitUint !=nil {
print("This is a defined temperature uint, so initialization succeeded")
}
//This is a defined temperature uint, so initialization succeeded
//构造失败
let unknownUint =TemperatureUint(sysmbol:"X")
if unknownUint ==nil {
print("This not a defined temperature unit, so initialization failed.")
}
//This not a defined temperature unit, so initialization failed.
/*带原始值的枚举类型的可失败构造器
带原始值的枚举类型会自带一个可失败构造器init?(rawValue:),该可失败构造器有一个名为rawValue的默认参数,
其类型和枚举类型的原始值类型一致,如果该参数的值能够和枚举类型成员所带的原始值匹配,则该构造器构造一个带此原始值的枚举成员,
否则构造失败。
*/
enum TemperatureUint1:Character {
//开尔文温度 摄氏温度 华式温度
case Kelvin ="K", Celsius ="C", Fahrenheit ="F"
}
let f1 =TemperatureUint1(rawValue:"F")
iff1 !=nil {
print("f1")
}
//f1
let unknown1 =TemperatureUint1(rawValue:"X")
if unknown1 ==nil {
print("unknown1")
}
//unknown1
/*类的可失败构造器
值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。比如在前面的例子中,
结构体Animal的可失败构造器触发失败的行为,甚至发生在species属性的值被初始化以前。而对类而言,就没有那么幸运了。
类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。
*/
class Product {
let name:String!
init?(name:String) {
self.name = name
if name.isEmpty {returnnil}
}
}
iflet bowTie =Product(name:"bow tie") {
print("The product's name is\(bowTie.name)")
}
//The product's name is bow tie
/*构造失败的传递
可失败构造器同样满足在构造器链中所描述的构造规则。其允许在同一类,结构体和枚举中横向代理其他的可失败构造器。
类似的,子类的可失败构造器也能向上代理基类的可失败构造器。
无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,
整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。
注意: 可失败构造器也可以代理调用其它的非可失败构造器。通过这个方法,你可以为已有的构造过程加入构造失败的条件。
*/
class CartItem:Product {
let quantity:Int!
init?(name:String, quantity:Int) {
self.quantity = quantity
super.init(name: name)
if quantity <1 {return nil }
}
}
iflet twoSocks =CartItem(name:"sock", quantity:2) {
print("Item:\(twoSocks.name), quantity:\(twoSocks.quantity)")
}
//Item: sock, quantity: 2
iflet zeroShirts =CartItem(name:"shirt", quantity:0) {
print("Item:\(zeroShirts.name), quantity:\(zeroShirts.quantity)")
}else {
print("Unable to initialize zero shirts")
}
//Unable to initialize zero shirts
iflet oneUnnamed =CartItem(name:"", quantity:1) {
print("Item:\(oneUnnamed.name), quantity:\(oneUnnamed.quantity)")
}else {
print("Unable to initialize one unnamed product")
}
//Unable to initialize one unnamed product
/*重写一个可失败构造器
就如同其它构造器一样,你也可以用子类的可失败构造器重写基类的可失败构造器。
或者你也可以用子类的非可失败构造器重写一个基类的可失败构造器。这样做的好处是,
即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。
注意当你用一个子类的非可失败构造器重写了一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。
一个非可失败的构造器永远也不能代理调用一个可失败构造器。
注意: 你可以用一个非可失败构造器重写一个可失败构造器,但反过来却行不通。
*/
class Document {
var name:String?
init() {}
init?(name:String) {
self.name = name
if name.isEmpty {returnnil }
}
}
class AutomaticallyNamedDocument:Document {
override init() {
super.init()
self.name ="[Untitled]"
}
//非可失败重写可失败构造器
overrideinit(name:String) {
super.init()
if name.isEmpty {
self.name ="[Untitled]"
}else {
self.name = name
}
}
}
class UntitledDocument:Document {
//非可失败调用可失败构造器需要强制解析(!)
override init() {
super.init(name:"[Untitled]")!
}
}
var untitledDc =UntitledDocument()
print(untitledDc.name!)//[Untitled]
可失败构造器 init!
通常来说我们通过在init关键字后添加问号的方式来定义一个可失败构造器,但你也可以使用通过在init后面添加惊叹号的方式来
定义一个可失败构造器(init!),该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。
你可以在 init?构造器中代理调用 init!构造器,反之亦然。你也可以用 init?重写 init!,反之亦然。
你还可以用 init代理调用init!,但这会触发一个断言:是否 init!构造器会触发构造失败?
必要构造器
在类的构造器前添加 required 修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
required init() {
// 在这里添加该必要构造器的实现代码
}
}
当子类重写基类的必要构造器时,必须在子类的构造器前同样添加required修饰符以确保当其它类继承该子类时,该构造器同为必要构造器。在重写基类的必要构造器时,不需要添加override修饰符:
class SomeSubclass: SomeClass {
required init() {
// 在这里添加子类必要构造器的实现代码
}
}
注意: 如果子类继承的构造器能满足必要构造器的需求,则你无需显示的在子类中提供必要构造器的实现。
class People {
let name:String!
requiredinit(name:String) {
self.name = name
}
}
class Student:People {
let sex:String
requiredinit(name:String, sex:String) {
self.sex = sex
super.init(name: name)
}
requiredinit(name:String) {
fatalError("init(name:) has not been implemented")
}
}
var student =Student(name:"阿斯玛", sex:"女")
print("student.name:\(student.name) student.sex:\(student.sex)")
//student.name: 阿斯玛 student.sex:女
通过闭包和函数来设置属性的默认值
如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。
每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,
最后将这个临时变量的值作为属性的默认值进行返回。
下面列举了闭包如何提供默认值的代码概要:
class SomeClass {
let someProperty: SomeType = {
//在这个闭包中给 someProperty 创建一个默认值
// someValue必须和 SomeType 类型相同
return someValue
}()
}
注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift需要立刻执行此闭包。如果你忽略了这对括号,
相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
注意:
如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,
就算这个属性有默认值也不允许。同样,你也不能使用隐式的self属性,或者调用其它的实例方法。
struct Checkerboard {
let boardColors:[Bool] = {
var temporaryBoard = [Bool]()
var isBack =false
for iin 1...10 {
for jin 1...10 {
temporaryBoard.append(isBack)
isBack = !isBack
}
isBack = !isBack
}
return temporaryBoard
}()
func surareIsBackAtRow(row:Int, column: Int) ->Bool {
returnboardColors[(row *10) + column]
}
}
let board = Checkerboard()
print(board.surareIsBackAtRow(0, column: 1))//true
print(board.surareIsBackAtRow(9, column: 9))// false