十四 初始化swift
初始化是类,结构体和枚举类型实例化的准备阶段。这个阶段设置这个实例存储的属性的初始化数值和作一些使用实例以前的准备以及必需要作的其余一些设置工做。数组
经过定义构造器(initializers)实现这个实例化过程,也就是建立一个新的具体实例的特殊方法。和Objective-C不同的是,Swift的构造器没有返回值。它们主要充当的角色是确保这个实例在使用以前能正确的初始化。闭包
类实例也能实现一个析构器(deinitializer),在类实例销毁以前作一些清理工做。app
一、存储属性的初始化ide
类和结构体必须在它们被建立时把它们全部的属性设置为合理的值。存储属性不能为不肯定状态函数
你能够在构造方法里面给一个属性设置一个初始值,或者在定义的时候给属性设置一个默认值工具
注意:当你对给一个属性分配一个默认值的时候,它会调用它相对应的初始化方法,这个值是对属性直接设置的,不会通知它对应的观察者ui
构造器this
构造器是建立一个具体类型实例的方法。最简单的构造器就是一个没有任何参数实例方法,写做init。spa
在下面的例子定义了一个叫Fahrenheit(华氏度)的新结构体,来储存转换成华氏度的温度。Fahrenheit结构体,有一个属性,叫temperature(温度),它的类型为Double(双精度浮点数):
struct Fahrenheit { var temperature: Double init() { temperature = 32.0 } } var f = Fahrenheit() print("The default temperature is \(f.temperature)° Fahrenheit")
这个结构体定义了一个单一的构造方法init,它没有任何参数,它储存的温度属性初始化为32.0度。(水在华氏度的温度状况下的冰点)。
属性的默认值
如上所述,你能够在构造器中设置它本身的储存属性的初始化值。或者在属性声明时,指定属性的默认值,你指定一个默认的属性值,会被分配到它定义的初始值。
注意:若是一个属性经常使用一样的初始化值 ,提供一个默认值会比在初始化使用一个默认值会更好。
一样的结果,可是默认值与属性的初始化在它定义地时候就牢牢地捆绑在一块儿。很简单地就能构造器更简洁,和可让你从默认值中推断出这个属性的类型。
struct Fahrenheit1 { var temperature = 32.0 }
2、自定义初始化(Customizing Initialization)
你能够根据输入的参数来自定义初始化过程和可选的属性类型,或者在初始化的时候修改静态属性。
初始化参数
你能够在构造器定义的时候提供一部分参数,在自定义初始化过程当中定义变量的类型和名称。
初始化参和函数或者方法参数同样有着一样的功能。
在下面的例子中,定义了一个结构体Celsius。储存了转换成摄氏度的温度,Celsius结构体实现了从不一样的温度初始化结构体的两个方法,init(fromFahrenheit:) 和init(fromKelvin:)。
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) let freezingPointOfWater = Celsius(fromKelvin: 273.15)
第一个构造器只有一个初始化参数,形参(External Parameter Names)fromFahrenheit,和实参(Local Parameter Names)fahrenheit。第二个构造器有一个单一的初始化参数,形参(External Parameter Names)fromKenvin,和实参(Local Parameter Names)kelvin。两个构造器都把单一的参数转换为摄氏度和储存到一个temperatureInCelsius的属性.
实参名(Local Parameter Names)和形参名(External Parameter Names)
和函数参数和方法参数同样,初始化参数拥有在构造器函数体使用的实参,和在调用时使用的形参.
然而,和函数或者方法不一样,构造器在圆括号前面没有一个识别函数名称。所以,构造器参数的名称和类型,在被调用的时候,很大程度上扮演一个被识别的重要角色。为此,在构造器中,当你没有提供形参名时,Swift就会为每个参数提供一个自动的形参名。这个形参名和实参名相同,就像和以前你写的每个初始化参数的hash符号同样。
注意:若是你在构造器中没有定义形参,提供一个下横线(_)做为区分形参和上面说描述的重写默认行为。
在下面的例子 ,定义了一个结构体Color,拥有三个静态属性red,green和blue。这些属性储存了从0.0到1.0的值,这些值表明红色 ,绿色和蓝色的深度。
Color提供了一个构造器,以及三个双精度(Double)类型的参数:
struct Color { var red = 0.0, green = 0.0, blue = 0.0 init(red: Double, green: Double, blue: Double) { self.red = red self.green = green self.blue = blue } }
不管何时,你建立一个Color实例,你必须使用每个颜色的形参来调用构造器:
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
可选类型
若是你储存属性使用的是自定义的类型在逻辑上容许值为空-或者他们的值并不在构造器中初始化,或者他们被容许为空。能够定义一个可选类型的属性。可选类型属性是一个自动初始化值为nil,表示这个属性有意在构造器中设置为“空值”(no value yet)。
在下面的例子中,定义了一个SurveryQuestion类,拥有一个可选的String属性response。
这个回答在他们调查问题在发布以前是没法知道的,因此response定义为类型String? ,或者叫可选String(optional String)。说明它会被自动分配一个默认值nil,意思为当surverQuestion初始化时还不存在。
在初始化时修改静态属性
当你在设置静态属性值时,只要在初始化完成以前,你均可以在初始化时随时修改静态属性。
注意:对于类的实例化,一个静态属性只能在初始化时被修改,这个初始化在类定义时已经肯定。
你能够重写SurveryQuestion例子,对于问题的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() beetsQuestion.response = "I also like beets. (But not with cheese.)"
三、默认构造器
Swift为每个结构或者基类提供了默认的构造器,来初始化它们所包含的全部属性。默认构造器将会建立一个新的实例而后将它们的属性设置为默认值。
下面的例子定义了一个叫ShoppingListItem的类,包含了名称,数量和是否已购买的属性,将会被用在购物清单中:
class ShoppingListItem { var name: String? var quantity = 1 var purchased = false } var item = ShoppingListItem()
由于ShoppingListItem类中全部的属性都有默认值,而且这个类是一个没有父类的基类,因此它默认拥有一个会将全部包含的属性设置为初始值的默认构造器。好比在这个例子中name属性是一个可选String属性,它会被默认设置为nil,尽管在代码中没有指明。上面的例子使用默认构造器建立了一个ShoppingListItem类,记作ShoppingListItem(),而后将它赋值给了变量item。
结构类型的成员逐一构造器
除了上面提到的默认构造器以外,结构类型还有另一种成员逐一完成初始化的构造器,能够在定义结构的时候直接指定每一个属性的初始值。
成员逐一构造器是一种为结构的成员属性进行初始化的简便方法。下面的例子定义了一个叫Size的结构,和两个属性分别叫width和height。每一个属性都是Double类型的而且被初始化为0.0。
由于每一个存储属性都有默认值,在Size结构建立一个实例的时候就能够自动调用这个成员逐一构造器init(width:height:):
struct Size { var width = 0.0, height = 0.0 } let twoByTwo = Size(width: 2.0, height: 2.0)
四、数值类型的构造器代理
在实例的初始化过程当中,构造器能够调用其余的构造器来完成初始化。这个过程叫构造器代理,能够避免多个构造器的重复代码。
对于数值类型和类来讲,构造器代理的工做形式是不同的。数值类型(结构和枚举)不支持继承,所以他们的构造器代理相对简单,由于它们只能使用本身的构造器代理。可是一个类能够继承自另一个类,因此类须要确保在初始化的时候将它全部的存储属性都设置为正确的值。
对于数值类型来讲,可使用self.init来调用其余构造器,注意只能在这个数值类型内部调用相应的构造器。
须要注意的是若是你为数值类型定义了一个构造器,你就不能再使用默认构造器了。这种特性能够避免当你提供了一个特别复杂的构造器的时候,另一我的误使用了默认构造器而出错。
注意:若是你想要同时使用默认构造器和你本身设置的构造器,不要将这两种构造器写在一块儿,而是使用扩展形式。
下面的示例定义了一个结构Rect来表示一个几何中的矩形。这个Rect结构须要另外两个结构来组成,包括Size和Point,初始值均为0.0:
struct Point { var x = 0.0, y = 0.0 }
如今你有三种初始化Rect结构的方式:直接使用为origin和size属性初始化的0值,给定一个指定的origin和size,或者使用中心点和大小来初始化。下面的例子包含了这三种初始化方式:
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) } }
五、类的继承和初始化
自定义初始化方法要先调用本身类默认初始化方法,本身重写默认初始化方法要先调用父类默认初始化方法
应该要先调用父类的构造器或者自身的默认构造器,以防止先给属性赋值了而后才调用父类或者自身的默认构造器把之前的赋值覆盖了
六、经过闭包或者函数来设置一个默认属性值
若是存储属性的默认值须要额外的特殊设置,可使用闭包或者函数来完成。
闭包或者函数会建立一个临时变量来做为返回值为这个属性赋值。下面是若是使用闭包赋值的一个示意代码:
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属性,或者其它实例方法。
下面的例子是一个叫Checkerboard的结构,是由游戏Checkers来的
image这个游戏是在一个10×10的黑白相间的格子上进行的。来表示这个游戏盘,使用了一个叫Checkerboard的结构,其中一个属性叫boardColors,是一个100个Bool类型的数组。true表示这个格子是黑色,false表示是白色。那么在初始化的时候能够经过下面的代码来初始化:
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] } }
当一个新的Checkerboard实例建立的时候,闭包会执行,而后boardColor的默认值将会被依次计算而且返回,而后做为结构的一个属性。经过使用squareIsBlackAtRow工具函数能够检测是否被正确设置:
let board = Checkerboard() print(board.squareIsBlackAtRow(0, column: 1)) print(board.squareIsBlackAtRow(9, column: 9))
十五 析构
在一个类的实例被释放以前,析构函数会被调用。用关键字deinit来定义析构函数,相似于初始化函数用init来定义。析构函数只适用于class类型。
一、析构过程原理
Swift 会自动释放再也不须要的实例以释放资源。如自动引用计数那一章描述,Swift 经过自动引用计数(ARC)处理实例的内存管理。一般当你的实例被释放时不须要手动地去清理。可是,当使用本身的资源时,你可能须要进行一些额外的清理。例如,若是建立了一个自定义的类来打开一个文件,并写入一些数据,你可能须要在类实例被释放以前关闭该文件。
在类的定义中,每一个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号:
deinit { // 执行析构过程 }
析构函数是在实例释放发生前一步被自动调用。不容许主动调用本身的析构函数。子类继承了父类的析构函数,而且在子类析构函数实现的最后,父类的析构函数被自动调用。即便子类没有提供本身的析构函数,父类的析构函数也老是被调用。
由于直到实例的析构函数被调用时,实例才会被释放,因此析构函数能够访问全部请求实例的属性,而且根据那些属性能够修改它的行为(好比查找一个须要被关闭的文件的名称)。
二、析构器操做
这里是一个析构函数操做的例子。这个例子是一个简单的游戏,定义了两种新类型,Bank和Player。Bank结构体管理一个虚拟货币的流通,在这个流通中Bank永远不可能拥有超过 10,000 的硬币。在这个游戏中有且只能有一个Bank存在,所以Bank由带有静态属性和静态方法的结构体实现,从而存储和管理其当前的状态。
struct Bank { static var coinsInBank = 10_000 static func vendCoins(var numberOfCoinsToVend: Int) -> Int { numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank) coinsInBank -= numberOfCoinsToVend return numberOfCoinsToVend } static func receiveCoins(coins: Int) { coinsInBank += coins } }
Bank根据它的coinsInBank属性来跟踪当前它拥有的硬币数量。银行还提供两个方法——vendCoins和receiveCoins——用来处理硬币的分发和收集。
vendCoins方法在 bank 分发硬币以前检查是否有足够的硬币。若是没有足够多的硬币,Bank返回一个比请求时小的数字(若是没有硬币留在 bank 中就返回 0)。vendCoins方法声明numberOfCoinsToVend为一个变量参数,这样就能够在方法体的内部修改数字,而不须要定义一个新的变量。vendCoins方法返回一个整型值,代表了提供的硬币的实际数目。
receiveCoins方法只是将 bank 的硬币存储和接收到的硬币数目相加,再保存回 bank。
Player类描述了游戏中的一个玩家。每个 player 在任什么时候刻都有必定数量的硬币存储在他们的钱包中。这经过 player 的coinsInPurse属性来体现:
class Player { var coinsInPurse: Int init(coins: Int) { coinsInPurse = Bank.vendCoins(coins) } func winCoins(coins: Int) { coinsInPurse += Bank.vendCoins(coins) } deinit { print("quit") Bank.receiveCoins(coinsInPurse) } }
每一个Player实例都由一个指定数目硬币组成的启动额度初始化,这些硬币在 bank 初始化的过程当中获得。若是没有足够的硬币可用,Player实例可能收到比指定数目少的硬币。
Player类定义了一个winCoins方法,该方法从银行获取必定数量的硬币,并把它们添加到玩家的钱包。Player类还实现了一个析构函数,这个析构函数在Player实例释放前一步被调用。这里析构函数只是将玩家的全部硬币都返回给银行:
var playerOne: Player? = Player(coins: 100) print("A new player has joined the game with \(playerOne!.coinsInPurse) coins") print("There are now \(Bank.coinsInBank) coins left in the bank")
一个新的Player实例随着一个 100 个硬币(若是有)的请求而被建立。这个Player实例存储在一个名为playerOne的可选Player变量中。这里使用一个可选变量,是由于玩家能够随时离开游戏。设置为可选使得你能够跟踪当前是否有玩家在游戏中。
由于playerOne是可选的,因此由一个感叹号(!)来修饰,每当其winCoins方法被调用时,coinsInPurse属性被访问并打印出它的默认硬币数目。
playerOne!.winCoins(2_000) print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins") print("The bank now only has \(Bank.coinsInBank) coins left")
这里,player 已经赢得了 2,000 硬币。player 的钱包如今有 2,100 硬币,bank 只剩余 7,900 硬币。
playerOne = nil print("The bank now has \(Bank.coinsInBank) coins")
玩家如今已经离开了游戏。这代表是要将可选的playerOne变量设置为nil,意思是“没有Player实例”。当这种状况发生的时候,playerOne变量对Player实例的引用被破坏了。没有其它属性或者变量引用Player实例,所以为了清空它占用的内存从而释放它。在这发生前一步,其析构函数被自动调用,其硬币被返回到银行。