构造过程
构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。构造过程是通过定义构造器(Initializers)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C 中的构造器不同,Swift的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
存储型属性的初始赋值
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态
注意:当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者(property observers)
①构造器
struct Fahrenheit { var temperature: Double init() {//无参构造函数 temperature = 32.0 } } var f = Fahrenheit() println("The default temperature is \(f.temperature) Fahrenheit") // 输出 "The default temperature is 32.0° Fahrenheit"
②默认属性值
struct Fahrenheit { var 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) // boilingPointOfWater.temperatureInCelsius 是 100.0 let freezingPointOfWater = Celsius(fromKelvin: 273.15) // freezingPointOfWater.temperatureInCelsius 是 0.0”
②内部和外部参数名
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 } func test(Rred: Double,Ggreen: Double){ print("大家好\(Ggreen)") } } let magenta = Color(red: 1.0,green: 0.0,blue: 1.0) let halfGray = Color(white: 0.5) magenta.test(22.0,Ggreen: 255.0)
③可选属性类型
如果你定制的类型包含一个逻辑上允许取值为空的存储型属性--不管是因为它无法在初始化时赋值,还是因为它可以在之后某个时间点可以赋值为空--你都需要将它定义为可选类型optional type。可选类型的属性将自动初始化为空nil,表示这个属性是故意在初始化时设置为空的。
class SurveyQuestion { var text: String var response: String?//可选类型的属性将自动初始化为空nil init(text: String) { self.text = text } func ask() { println(text) } } let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?") cheeseQuestion.ask() // prints "Do you like cheese?" cheeseQuestion.response = "Yes,I do like cheese." println("问题:\(cheeseQuestion.text) 回答:\(cheeseQuestion.response!)")
只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
注意:对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
class SurveyQuestion2 { let text: String//常量属性不能在子类中修改,只能在父类的构造过程中修改 var response: String? init(text: String) { self.text = text } func ask() { println(text) } } let beetsQuestion = SurveyQuestion2(text: "How about beets?") beetsQuestion.ask() // prints "How about beets?" beetsQuestion.response = "I also like beets. (But not with cheese.)"
默认构造器
Swift 将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。
class ShoppingListItem { var name: String? var quantity = 1 var purchased = false } var item = ShoppingListItem() //由于ShoppingListItem类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器(尽管代码中没有显式为name属性设置默认值,但由于name是可选字符串类型,它将默认设置为nil)
结构体的逐一成员构造器
如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。来初始化结构体新实例里成员属性的快捷方法
下面的结构体Size中,由于这两个存储型属性都有默认值,结构体Size自动获得了一个逐一成员构造器 init(width:height:)。
struct Size { var width = 0.0,height = 0.0 } let twoByTwo = Size(width: 2.0,height: 2.0)
值类型的构造器代理
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理任务给本身提供的其它构造器。类则不同,它可以继承自其它类(请参考继承),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
struct Size { var width = 0.0,height = 0.0 } struct Point { var x = 0.0,y = 0.0 }
你可以通过以下三种方式为Rect创建实例--使用默认的0值来初始化origin和size属性;使用特定的origin和size实例来初始化;使用特定的center和size来初始化。在下面Rect结构体定义中,我们为着三种方式提供了三个自定义的构造器:
struct Rect { var size = Size() var origin = Point() 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) //代理给init(origin:size:) self.init(origin: Point(x: originX,y: originY),size: size) } }
类的继承和构造过程
类里面的所有存储型属性--包括所有继承自父类的属性--都必须在构造过程中设置初始值。
Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。
①指定构造器
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。每一个类都必须拥有至少一个指定构造器。
②便利构造器
你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
构造器链
//Swift采用以下三条规则来限制构造器之间的代理调用
//②规则2:便利构造器必须调用同一类中定义的其它构造器
//③规则3:便利构造器必须最终以调用一个指定构造器结束
一个更方便记忆的方法是:
>>>>>指定构造器必须总是向上代理
>>>>>便利构造器必须总是横向代理
两段式构造过程(第一阶段:存储型属性设值 第二阶段:存储型属性设值)
第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。
注意:Swift的两段式构造过程跟Objective-C中的构造过程类似。最主要的区别在于阶段 1,Objective-C给每一个属性赋值0或空值(比如说0或nil)。Swift的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以0或nil作为合法默认值的情况。
Swift 编译器将执行 4种有效的安全检查,以确保两段式构造过程能顺利完成:
安全检查 1
指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。
安全检查 2
指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
安全检查 3
便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
安全检查 4
构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self的值。
以下是两段式构造过程中基于上述安全检查的构造流程展示:
阶段 1
某个指定构造器或便利构造器被调用;
完成新实例内存的分配,但此时内存还没有被初始化;
指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;
这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。
阶段 2
从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。
构造器的继承和重载
跟 Objective-C中的子类不同,Swift 中的子类不会默认继承父类的构造器。Swift的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。
子类中可以重载父类的构造器
①若重载的是指定构造器:你可以在子类里重载它的实现,并在自定义版本的构造器中调用父类版本的构造器。
②若重载的是便利构造器:你的重载过程必须通过调用同一类中提供的其它指定构造器来实现。
注意:与方法、属性和下标不同,在重载构造器时你没有必要使用关键字override。
自动构造器的继承
子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。
①如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
②如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。
指定构造器和便利构造器的语法
①指定构造器
init(parameters) { //statements }
②便利构造器(init前加convenience关键字)
convenience init(parameters) { // statements }
指定构造器和便利构造器实战
基类Food
class Food { var name: String //指定构造器 init(name: String) { self.name = name } //无参便利构造器(便利构造器必须调用同类中的其他构造器,且最终必须调用指定构造器) convenience init() { self.init(name: "[Unnamed]") } } let namedMeat = Food(name: "Bacon") // namedMeat 的名字是 "Bacon" let mysteryMeat = Food() // mysteryMeat 的名字是 [Unnamed]
类RecipeIngredient继承自Food
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继承了父类Food的便利构造器init(),并将任务代理给RecipeIngredient版本的init(name: String)而不是Food提供的版本。 } let oneMysteryItem = RecipeIngredient()//此时会调用父类的便利构造器init() [如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。] let oneBacon = RecipeIngredient(name: "Bacon") let sixEggs = RecipeIngredient(name: "Eggs",quantity: 6)
类ShoppingListItem继承自RecipeIngredient
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 in breakfastList { println(item.description) } // 1 x orange juice ✔ // 1 x bacon ✘ // 6 x eggs ✘
如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。
class SomeClass { let someProperty: Int = { // 在这个闭包中给 someProperty 创建一个默认值 // someValue 必须和 SomeType 类型相同 return 2 }() }
注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的self属性,或者调用其它的实例方法。
struct Checkerboard { let boardColors: [Bool] = { var temporaryBoard = [Bool]() var isBlack = false for i in 1...10 { for j in 1...10 { temporaryBoard.append(isBlack) isBlack = !isBlack } isBlack = !isBlack } return temporaryBoard }() func squareIsBlackAtRow(row: Int,column: Int) -> Bool { return boardColors[(row * 10) + column] } } let board = Checkerboard() println(board.squareIsBlackAtRow(0,column: 1)) // 输出 "true" println(board.squareIsBlackAtRow(9,column: 9)) // 输出 "false"