存儲屬性
在其最簡單的形式中,存儲屬性是作為特定類或結(jié)構(gòu)的實例的一部分存儲的常量或變量。 存儲的屬性可以是變量存儲屬性(由var關(guān)鍵字引入)或常量存儲屬性(由關(guān)鍵字引入)柠掂。
您可以為存儲屬性提供默認值肝谭,作為其定義的一部分黔姜,如默認屬性值中所述俄占。 您還可以在初始化期間設(shè)置和修改存儲屬性的初始值腔长。 即使對于常量存儲的屬性也是如此辐烂,如在初始化期間分配常量屬性中所述遏插。
下面的示例定義了一個稱為FixedLengthRange的結(jié)構(gòu),它描述了一系列整數(shù)纠修,其范圍長度在創(chuàng)建后無法更改:
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
FixedLengthRange的實例具有稱為firstValue的變量存儲屬性和稱為length的常量存儲屬性胳嘲。 在上面的示例中,長度在創(chuàng)建新范圍時初始化扣草,并且之后無法更改了牛,因為它是常量屬性颜屠。
常量結(jié)構(gòu)實例的存儲屬性
如果創(chuàng)建結(jié)構(gòu)的實例并將該實例分配給常量,那么即使它們被聲明為變量屬性鹰祸,也不能修改實例的屬性:
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
因為rangeOfFourItems被聲明為一個常量(使用let關(guān)鍵字)甫窟,所以不可能改變它的firstValue屬性,即使firstValue是一個變量屬性蛙婴。
這種行為是由于結(jié)構(gòu)是值類型粗井。 當值類型的實例被標記為常量時,其所有屬性也是如此街图。
對于作為引用類型的類則不是這樣浇衬。 如果將引用類型的實例指定給常量,則仍然可以更改該實例的變量屬性餐济。
延遲存儲屬性
延遲存儲屬性是一種屬性耘擂,其初始值不會在第一次使用時計算。 你通過在聲明之前寫入lazy修飾符來指示一個惰性存儲屬性絮姆。
注意:
您必須始終將lazy屬性聲明為變量(使用var關(guān)鍵字)醉冤,因為它的初始值可能無法在實例初始化完成后檢索。 常量屬性在初始化完成之前必須始終具有值滚朵,因此不能聲明為延遲冤灾。
下面的示例使用延遲存儲屬性,以避免不必要的復雜類的初始化辕近。 此示例定義了兩個名為DataImporter和DataManager的類韵吨,這兩個類都不完整顯示:
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
因為它用lazy修飾符標記,所以只有在首次訪問importer屬性時才會創(chuàng)建importer屬性的DataImporter實例移宅,例如查詢其fileName屬性時:
print(manager.importer.fileName)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
如果標記有l(wèi)azy修飾符的屬性由多個線程同時訪問归粉,并且該屬性尚未初始化,則不能保證該屬性只被初始化一次漏峰。
存儲屬性和實例變量
如果你有Objective-C的經(jīng)驗糠悼,你可能知道它提供了兩種方法來存儲值和引用作為類實例的一部分。 除了屬性浅乔,您可以使用實例變量作為存儲在屬性中的值的后備存儲倔喂。
Swift將這些概念統(tǒng)一到單個屬性聲明中。 Swift屬性沒有對應的實例變量靖苇,并且不直接訪問屬性的后備存儲席噩。 這種方法避免了對在不同上下文中如何訪問值的混淆,并將屬性的聲明簡化為單個確定的語句贤壁。 有關(guān)屬性的所有信息(包括其名稱悼枢,類型和內(nèi)存管理特性)在單個位置中定義為類型定義的一部分。
計算屬性
除了存儲的屬性脾拆,類馒索,結(jié)構(gòu)和枚舉可以定義計算屬性莹妒,實際上不存儲值。 相反绰上,它們提供了一個getter和一個可選的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)"
速記組合聲明
如果計算屬性的setter未定義要設(shè)置的新值的名稱,則使用缺省名稱newValue渔期。 這里有一個替代版本的Rect結(jié)構(gòu)运吓,它利用了這個簡寫符號:
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)
}
}
}
只讀計算屬性
具有g(shù)etter但沒有setter的計算屬性稱為只讀計算屬性。 只讀計算屬性始終返回一個值疯趟,可以通過點語法訪問拘哨,但不能設(shè)置為其他值。
注意:
您必須使用var關(guān)鍵字聲明計算屬性(包括只讀計算屬性)作為變量屬性信峻,因為它們的值不是固定的倦青。 let關(guān)鍵字僅用于常量屬性,表示一旦將它們的值設(shè)置為實例初始化的一部分盹舞,它們的值就無法更改产镐。
您可以通過刪除get關(guān)鍵字及其大括號來簡化只讀計算屬性的聲明:
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"
屬性觀察者
屬性觀察者觀察并響應屬性值的變化。 每次設(shè)置屬性的值時踢步,都會調(diào)用屬性觀察器癣亚,即使新值與屬性的當前值相同。
您可以將屬性觀察器添加到您定義的任何存儲的屬性获印,lazy存儲屬性除外述雾。 您還可以通過覆蓋子類中的屬性,將屬性觀察器添加到任何繼承的屬性(無論是存儲還是計算)兼丰。 您不需要為未覆蓋的計算屬性定義屬性觀察器玻孟,因為您可以在計算屬性的setter中觀察并響應對其值的更改。 屬性覆蓋在覆蓋中描述鳍征。
您可以選擇在屬性上定義這兩個觀察者之一:
- willSet在值被存儲之前被調(diào)用黍翎。
- didSet在存儲新值之后立即調(diào)用。
如果你實現(xiàn)一個willSet觀察者艳丛,它作為一個常量參數(shù)傳遞新的屬性值匣掸。 您可以為此參數(shù)指定名稱,作為您的willSet實現(xiàn)的一部分氮双。 如果不在實現(xiàn)中寫入?yún)?shù)名稱和括號旺聚,那么該參數(shù)的缺省參數(shù)名稱為newValue。
類似地眶蕉,如果你實現(xiàn)一個didSet觀察者,它傳遞一個常量參數(shù)包含舊的屬性值唧躲。 您可以命名參數(shù)或使用默認參數(shù)名稱oldValue造挽。 如果您為自己的didSet觀察器中的屬性賦值碱璃,則您分配的新值將替換剛剛設(shè)置的值。
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
注意:
如果將一個具有觀察者的屬性作為一個in-out參數(shù)傳遞給一個函數(shù)饭入,willSet和didSet觀察者總是被調(diào)用嵌器。 這是因為用于輸入輸出參數(shù)的復制輸入輸出存儲器模型:該值始終寫回函數(shù)結(jié)尾處的屬性。 有關(guān)輸入 - 輸出參數(shù)行為的詳細討論谐丢,請參閱輸入 - 輸出參數(shù)爽航。