屬性 (Properties)
自從蘋果2014年發(fā)布Swift,到現(xiàn)在已經(jīng)兩年多了,而Swift也來到了3.1版本。去年利用工作之余,共花了兩個(gè)多月的時(shí)間把官方的Swift編程指南看完?,F(xiàn)在整理一下筆記,回顧一下以前的知識(shí)。有需要的同學(xué)可以去看官方文檔>>。
存儲(chǔ)屬性作為實(shí)例的一部分,存在常量和變量的值,而計(jì)算屬性是計(jì)算一個(gè)值,而不是存儲(chǔ)一個(gè)值。計(jì)算屬性由類、結(jié)構(gòu)和枚舉提供;而存儲(chǔ)屬性只能由類和結(jié)構(gòu)提供。
存儲(chǔ)和計(jì)算屬性通常有關(guān)聯(lián)著一個(gè)實(shí)例。然而,屬性可以關(guān)聯(lián)類型。
另外,可以定義屬性觀察者來監(jiān)測屬性值的變化。屬性觀察者可以添加到自己定義的存儲(chǔ)屬性中,也可以添加到從父類繼承的屬性中。
存儲(chǔ)屬性 (Stored Properties)
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8
firstValue是變量存儲(chǔ)屬性,length是常量存儲(chǔ)屬性。
常量結(jié)構(gòu)實(shí)例的存儲(chǔ)屬性 (Stored Properties of Constant Structure Instances)
如果創(chuàng)建一個(gè)結(jié)構(gòu)實(shí)例,并賦給一個(gè)常量,那么就不能修改實(shí)例的屬性,即使這個(gè)屬性是一個(gè)變量屬性:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property
因?yàn)?code>rangeOfFourItems被聲明為常量,所以不能修改firstValue屬性,即使firstValue是變量屬性。
出現(xiàn)這種現(xiàn)象,其實(shí)是因?yàn)榻Y(jié)構(gòu)是值類型。只要實(shí)例是常量,那么它的屬性也都是常量。
而對于引用類型的類而言,把一個(gè)引用類型的實(shí)例賦值給一個(gè)常量,我們?nèi)稳豢梢孕薷膶?shí)例的變量屬性。
懶惰存儲(chǔ)屬性 (Lazy Stored Properties)
一個(gè)屬性在第一次使用之前,他的初始值沒有被計(jì)算,那么這個(gè)屬性稱為懶惰存儲(chǔ)屬性。
注意:懶惰存儲(chǔ)屬性必須聲明為一個(gè)變量,因?yàn)閷?shí)例初始化完成后,懶惰屬性可能還沒被獲取。常量屬性在實(shí)例初始化完成之前必須有一個(gè)值,所有不能聲明為懶惰屬性。
當(dāng)一個(gè)屬性的初始值依賴于外部因素,而外部因素的值需要在實(shí)例初始化完成之后才能確定,那么把這個(gè)屬性聲明為懶惰屬性非常有用。當(dāng)一個(gè)屬性的初始值需要很復(fù)雜的計(jì)算時(shí),懶惰屬性也非常有用。
下面這個(gè)例子使用懶惰屬性來避免不必要的一個(gè)復(fù)雜類的初始化。
class DataImporter {
/*
DataImporter is a class to import data from an external file.
The class is assumed to take a non-trivial amount of time to initialize.
*/
var fileName = "data.txt"
// the DataImporter class would provide data importing functionality here
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// the DataManager class would provide data management functionality here
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// the DataImporter instance for the importer property has not yet been created
DataManager定義了一個(gè)DataImporter實(shí)例,這里假設(shè)初始化DataImporter實(shí)例需要很長時(shí)間。DataManager實(shí)例可以自己管理數(shù)據(jù),而不必使用DataImporter實(shí)例來打開文件,所以當(dāng)創(chuàng)建DataManger實(shí)例時(shí),無需把DataImporter實(shí)例同時(shí)創(chuàng)建,而是在第一次使用的時(shí)候才創(chuàng)建。
print(manager.importer.fileName)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
注意:如果一個(gè)懶惰屬性被多個(gè)線程同時(shí)訪問,而且這個(gè)屬性還沒有被初始化時(shí),不能保證這個(gè)屬性只被初始化一次。
計(jì)算屬性 (Computed Properties)
計(jì)算屬性提供了一個(gè)getter和一個(gè)可選的setter方法來間接地獲取和設(shè)置其他屬性和其他值。
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"
這個(gè)例子定義了三個(gè)結(jié)構(gòu):
-
Point封裝了一個(gè)點(diǎn)的x和y坐標(biāo) -
Size封裝了寬和高 -
Rect使用原點(diǎn)和尺寸定義了一個(gè)矩形
Rect還定義了一個(gè)計(jì)算屬性center。矩形的中心可以同原點(diǎn)和尺寸計(jì)算出來,所以不必用存儲(chǔ)屬性來定義矩形的中心。Rect為計(jì)算屬性定義了一個(gè)自定義的getter和setter。
上面這個(gè)例子中創(chuàng)建了一個(gè)正方形square,如下圖中的藍(lán)色矩形。然后square的center屬性通過點(diǎn)語法被訪問,會(huì)使center的getter方法被調(diào)用,用來獲取屬性的當(dāng)前值,getter會(huì)返回一個(gè)新的Point代表正方形的中心(當(dāng)前是(5, 5))。
接著center被設(shè)置為(15, 15),把正方形往右上角移動(dòng),如下圖的橙色正方形。設(shè)置center屬性會(huì)調(diào)用setter方法,修改x和y的值,把正方形移到一個(gè)新的位置。

簡寫Setter的定義 (Shorthand Setter Declaration)
如果setter方法沒有給新的值定義名字,我們可以使用一個(gè)默認(rèn)名字newValue。
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
只讀計(jì)算屬性 (Read-Only Computed Properties)
一個(gè)計(jì)算屬性只有g(shù)etter沒有setter,那么這個(gè)計(jì)算屬性就是只讀計(jì)算屬性。只讀計(jì)算屬性智能返回一個(gè)值,通過點(diǎn)語法訪問,但是不能設(shè)置新的值。
注意:必須用var把計(jì)算屬性(包括只讀計(jì)算屬性)定義為可變屬性,因?yàn)樗麄兊闹凳遣还潭ǖ摹?/p>
定義只讀屬性時(shí),我們可以把get關(guān)鍵字和大括號(hào)省略:
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"
屬性觀察者 (Property Observers)
屬性觀察者觀察屬性值得變化,并作出響應(yīng)。只要屬性的值被設(shè)置,屬性觀察者就會(huì)被調(diào)用,即使新的值和屬性的當(dāng)前值一樣。
我們可以把屬性觀察者添加到自己定義的任何存儲(chǔ)屬性中,除了懶惰存儲(chǔ)屬性。也可以通過重寫父類的屬性來添加到父類的屬性中。不必為沒有重寫的計(jì)算屬性定義屬性觀察者,因?yàn)槲覀兛梢栽谟?jì)算屬性的setter方法觀察他的值的變化,并作出響應(yīng)。
兩種觀察屬性值變化的方法:
-
willSet:新的值被存儲(chǔ)之前調(diào)用 -
didSet:新的值被存儲(chǔ)之后馬上調(diào)用
如果實(shí)現(xiàn)一個(gè)willSet觀察者,會(huì)把新的值作為一個(gè)常量參數(shù)??梢詾檫@個(gè)新的值指定一個(gè)名字,如果沒有指定,那么默認(rèn)是newValue。
如果實(shí)現(xiàn)了一個(gè)didSet觀察者,會(huì)把舊的值作為一個(gè)常量參數(shù)??梢詾檫@個(gè)舊的值指定一個(gè)名字,如果沒有指定,那么默認(rèn)是oldValue。如果在didSet觀察者里面給屬性賦一個(gè)新值,那么這個(gè)新值會(huì)替換掉剛剛設(shè)置的那個(gè)值。
注意:當(dāng)父類的屬性在子類的初始化器被設(shè)置,那么父類屬性的willSet和didSet會(huì)在父類的初始化器調(diào)用之后被調(diào)用。在設(shè)置父類自己的屬性時(shí),父類的willSet和didSet在父類的初始化器調(diào)用之前不會(huì)被調(diào)用。
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
注意:如果把含有觀察者的屬性作為一個(gè)in-out參數(shù)傳入一個(gè)方法,那么willSet和didSet總是會(huì)被調(diào)用,這是因?yàn)閕n-out參數(shù)的"copy-in copy-out"內(nèi)存模式:在方法結(jié)束時(shí),這個(gè)值會(huì)被返回被屬性。
全局和本地變量 (Global and Local Variables)
全局變量是在任何方法、閉包和類型上下文之外定義的。而局部變量是在方法、閉包和類型上下文之內(nèi)定義的。
注意:全局常量和變量都是懶惰地計(jì)算,類似懶惰存儲(chǔ)屬性。但是全局常量和變量不需要使用lazy標(biāo)記。本地常量和變量從來都不是懶惰的計(jì)算。
類型屬性 (Type Properties)
實(shí)例屬性是屬于一個(gè)特定類型的實(shí)例。我們可以定義屬于類型的屬性。
注意:不同于實(shí)例屬性,我們必須給類型屬性一個(gè)默認(rèn)值。因?yàn)轭愋蜎]有初始化器來給類型屬性賦值。類型存儲(chǔ)屬性只要在第一次被訪問的時(shí)候才會(huì)初始化,并且保證只會(huì)初始化一次,即使是在多線程中同時(shí)被訪問,不需要使用lazy關(guān)鍵字標(biāo)記。
類型屬性語法 (Type Property Syntax)
使用static關(guān)鍵字來定義類型屬性,對于class類型的計(jì)算類型屬性,可以使用class來定義,可以讓子類重新父類的實(shí)現(xiàn)。
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
查詢和設(shè)置類型屬性 (Querying and Setting Type Properties)
就像實(shí)例屬性一樣,類型屬性也是通過點(diǎn)語法來查詢和設(shè)置的:
print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"
第十部分完。下個(gè)部分:【Swift 3.1】11 - 方法 (Functions)
如果有錯(cuò)誤的地方,歡迎指正!謝謝!