屬性將值與特定的類买优,結(jié)構(gòu)或枚舉關(guān)聯(lián)。存儲的屬性將常量和變量值存儲為實(shí)例的一部分挺举,而計(jì)算的屬性將計(jì)算(而不是存儲)值杀赢。計(jì)算的屬性由類,結(jié)構(gòu)和枚舉提供湘纵。存儲的屬性僅由類和結(jié)構(gòu)提供脂崔。
存儲和計(jì)算的屬性通常與特定類型的實(shí)例相關(guān)聯(lián)。但是梧喷,屬性也可以與類型本身關(guān)聯(lián)砌左。這樣的屬性稱為類型屬性。
此外铺敌,您可以定義屬性觀察器以監(jiān)視屬性值的更改汇歹,您可以使用自定義操作對其進(jìn)行響應(yīng)〕テ荆可以將屬性觀察器添加到您自己定義的存儲屬性中产弹,也可以添加到子類從其超類繼承的屬性中。
您還可以使用屬性包裝器在多個屬性的getter和setter中重用代碼弯囊。
存儲屬性
以最簡單的形式取视,存儲的屬性是作為特定類或結(jié)構(gòu)的實(shí)例的一部分存儲的常量或變量硝皂。存儲屬性可以是變量存儲屬性(由var關(guān)鍵字引入)或常量存儲屬性(由let關(guān)鍵字引入)。
值類型的存儲屬性作谭,如果創(chuàng)建值類型的實(shí)例(結(jié)構(gòu)體)并將該實(shí)例分配給常量稽物,則即使它們被聲明為變量屬性,也無法修改實(shí)例的屬性折欠。
惰性存儲屬性
您必須始終將惰性屬性聲明為變量(使用var關(guān)鍵字)贝或,因?yàn)橹钡綄?shí)例初始化完成后才可能檢索其初始值。常量屬性在初始化完成之前必須始終具有一個值锐秦,因此不能聲明為惰性咪奖。當(dāng)屬性的初始值需要復(fù)雜的或計(jì)算量大的設(shè)置,除非或直到需要時才應(yīng)執(zhí)行酱床,否則惰性屬性也很有用羊赵。
存儲屬性和實(shí)例變量
如果您有使用Objective-C的經(jīng)驗(yàn),您可能會知道它提供了兩種將值和引用存儲為類實(shí)例的一部分的方法扇谣。除了屬性之外昧捷,您還可以將實(shí)例變量用作存儲在屬性中的值的后備存儲。
Swift將這些概念統(tǒng)一為一個屬性聲明罐寨。Swift屬性沒有相應(yīng)的實(shí)例變量靡挥,并且不能直接訪問該屬性的后備存儲。這種方法避免了在不同上下文中如何訪問值的困惑鸯绿,并將屬性的聲明簡化為單個確定的語句跋破。有關(guān)屬性的所有信息(包括名稱,類型和內(nèi)存管理特征)都在單個位置中定義瓶蝴,作為類型定義的一部分毒返。
計(jì)算屬性
除了存儲的屬性外,類舷手,結(jié)構(gòu)和枚舉還可以定義計(jì)算的屬性饿悬,而實(shí)際上并不存儲值。相反聚霜,它們提供了一個getter和一個可選的setter來間接檢索和設(shè)置其他屬性和值狡恬。
只讀計(jì)算屬性
具有g(shù)etter但沒有setter的計(jì)算屬性稱為只讀計(jì)算屬性。只讀的計(jì)算屬性始終返回一個值蝎宇,并且可以通過點(diǎn)語法進(jìn)行訪問弟劲,但不能將其設(shè)置為其他值。
您必須使用var關(guān)鍵字將計(jì)算屬性(包括只讀計(jì)算屬性)聲明為變量屬性姥芥,因?yàn)樗鼈兊闹凳枪潭ǖ耐闷颉T搇et關(guān)鍵字僅用于恒定的特性,以表示它們的值不能一朝被改變已經(jīng)設(shè)置為實(shí)例初始化的一部分。
屬性觀察者
屬性觀察者觀察并響應(yīng)屬性值的變化庸追。每次設(shè)置屬性值時都會調(diào)用屬性觀察者霍骄,即使新值與屬性的當(dāng)前值相同也是如此。
您可以在以下位置添加屬性觀察器:
定義的存儲屬性淡溯、繼承的存儲屬性读整、繼承的計(jì)算屬性
對于繼承的屬性,可以通過在子類中覆蓋該屬性來添加屬性觀察器咱娶。對于您定義的計(jì)算屬性米间,請使用屬性的setter觀察并響應(yīng)值更改,而不是嘗試創(chuàng)建觀察者膘侮。
您可以選擇在屬性上定義這些觀察者之一或全部:
willSet?在值存儲之前被調(diào)用屈糊。
didSet?新值存儲后立即調(diào)用。
如果實(shí)現(xiàn)willSet觀察者琼了,則它將新的屬性值作為常量參數(shù)傳遞逻锐。您可以在實(shí)現(xiàn)中為此參數(shù)指定名稱willSet。如果您未在實(shí)現(xiàn)中編寫參數(shù)名稱和括號雕薪,則該參數(shù)的默認(rèn)參數(shù)名稱為newValue昧诱。同樣,如果實(shí)現(xiàn)didSet觀察者蹦哼,則會傳遞一個包含舊屬性值的常量參數(shù)鳄哭。您可以命名參數(shù)或使用默認(rèn)參數(shù)名稱oldValue要糊。如果您在其自己的didSet觀察器中為屬性分配值纲熏,則分配的新值將替換剛剛設(shè)置的值。
在調(diào)用超類初始化器之后锄俄,在子類初始化器中設(shè)置屬性時局劲,將調(diào)用超類屬性的willSet和didSet觀察者。在調(diào)用超類初始化器之前奶赠,類在設(shè)置其自己的屬性時不會調(diào)用它們鱼填。
如果將具有觀察者的屬性作為輸入輸出參數(shù)傳遞給函數(shù),則始終調(diào)用willSet和didSet觀察者毅戈。這是由于用于in-out參數(shù)的in-in copy-out內(nèi)存模型:該值始終在函數(shù)末尾寫回到該屬性苹丸。
屬性Wrapper
屬性包裝器在管理屬性存儲方式的代碼與定義屬性的代碼之間增加了一層隔離。例如苇经,如果您具有提供線程安全檢查或?qū)⑵浠A(chǔ)數(shù)據(jù)存儲在數(shù)據(jù)庫中的屬性赘理,則必須在每個屬性上編寫該代碼。使用屬性包裝器時扇单,定義包裝器時商模,只需編寫一次管理代碼,然后通過將其應(yīng)用于多個屬性來重用該管理代碼。要定義屬性包裝器施流,您需要創(chuàng)建定義屬性的結(jié)構(gòu)响疚,枚舉或類wrappedValue。在下面的代碼中瞪醋,該TwelveOrLess結(jié)構(gòu)確保包裝的值始終包含小于或等于12的數(shù)字忿晕。如果您要求存儲更大的數(shù)字,則改為存儲12趟章。
@propertyWrapper
struct TwelveOrLess { ? ?
????private var number: Int ? ?
????init() { self.number = 0 } ? ?
????var wrappedValue: Int { ? ? ? ?
????????get { return number } ? ? ? ?
????????set { number = min(newValue, 12) } ? ?
????}
}
struct SmallRectangle { ? ? ? ? ? ? @TwelveOrLess var height: Int ? ? ? ? ? ? @TwelveOrLess var width: Int ? ? ? ? }? ? ? ? ??
var rectangle = SmallRectangle() ?? ? ? ? ? ? ? ? rectangle.height = 10 ? ? ? ? print(rectangle.height) //10? ? ? ? rectangle.height = 24 ? ? ? ? print(rectangle.height) //12
上例中的聲明number將變量標(biāo)記為private杏糙,以確保number僅在的實(shí)現(xiàn)中使用TwelveOrLess。在其他地方編寫的代碼使用的getter和setter訪問值wrappedValue蚓土,并且不能number直接使用宏侍。
全局和局部變量
上面描述的用于計(jì)算和觀察屬性的功能也可用于全局變量和局部變量。全局變量是在任何函數(shù)蜀漆,方法谅河,閉包或類型上下文之外定義的變量。局部變量是在函數(shù)确丢,方法或閉包上下文中定義的變量绷耍。
在上一章中遇到的全局變量和局部變量都已存儲。與存儲的屬性一樣,存儲的變量為某種類型的值提供存儲,并允許設(shè)置和檢索該值瘸羡。
但是印蔬,您還可以在全局或局部范圍內(nèi)定義計(jì)算變量并為存儲的變量定義觀察者。計(jì)算變量計(jì)算它們的值精刷,而不是存儲它,并且它們的寫法與計(jì)算屬性的寫法相同。
全局常量和變量總是以與惰性存儲屬性類似的方式延遲計(jì)算胆数。與惰性存儲的屬性不同,全局常量和變量不需要使用lazy修飾符進(jìn)行標(biāo)記互墓。局部常量和變量永遠(yuǎn)不會延遲計(jì)算必尼。
類型屬性
類型屬性是屬于特定類型的實(shí)例的屬性。每次創(chuàng)建該類型的新實(shí)例時篡撵,它都有自己的一組屬性值判莉,與其他任何實(shí)例分開。
您還可以定義屬于類型本身的屬性育谬,而不是屬于該類型的任何一個實(shí)例的屬性券盅。無論您創(chuàng)建了多少個該類型的實(shí)例,這些屬性將永遠(yuǎn)只有一個副本斑司。這些類型的屬性稱為類型屬性渗饮。類型屬性對于定義特定類型的所有實(shí)例通用的值很有用但汞,例如,所有實(shí)例可以使用的常量屬性(例如C中的靜態(tài)常量)互站,或存儲所有實(shí)例的全局值的變量屬性這種類型的(類似于C中的靜態(tài)變量)私蕾。
存儲的類型屬性可以是變量或常量。計(jì)算類型屬性始終以與計(jì)算實(shí)例屬性相同的方式聲明為變量屬性胡桃。
與存儲實(shí)例屬性不同踩叭,您必須始終為類型屬性賦予默認(rèn)值。這是因?yàn)轭愋捅旧頉]有可在初始化時將值分配給存儲的type屬性的初始化程序翠胰。存儲的類型屬性在其第一次訪問時被延遲初始化容贝。保證它們僅被初始化一次,即使同時被多個線程訪問也是如此之景,并且不需要用lazy修飾符標(biāo)記它們斤富。
類型屬性語法
在C和Objective-C中,您將靜態(tài)常量和與類型關(guān)聯(lián)的變量定義為全局靜態(tài)變量锻狗。但是满力,在Swift中,類型屬性被寫為類型定義的一部分轻纪,位于類型的外部花括號內(nèi)油额,并且每個類型屬性都明確地限定為其支持的類型。
您可以使用static關(guān)鍵字定義類型屬性刻帚。對于類類型的計(jì)算類型屬性潦嘶,可以改用class關(guān)鍵字來允許子類覆蓋超類的實(shí)現(xiàn)。下面的示例顯示了存儲和計(jì)算的類型屬性的語法:
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
? ? }
}
上面的計(jì)算類型屬性示例僅適用于只讀計(jì)算類型屬性崇众,但您也可以使用與計(jì)算實(shí)例屬性相同的語法來定義讀寫計(jì)算類型屬性掂僵。
查詢和設(shè)置類型屬性
與實(shí)例屬性一樣,查詢類型屬性并使用點(diǎn)語法對其進(jìn)行設(shè)置校摩。但是看峻,將查詢類型屬性并將其設(shè)置在type上阶淘,而不是在該類型的實(shí)例上進(jìn)行設(shè)置衙吩。例如:
struct AudioChannel { ? ?
????static let thresholdLevel = 10 ? ?
????static var maxInputLevelForAllChannels = 0 ? ?
????var currentLevel: Int = 0 { ? ? ? ?
????????didSet { ? ? ? ? ? ?
????????????if currentLevel > AudioChannel.thresholdLevel { ? ? ? ? ? ? ? ?
????????????// cap the new audio level to the threshold level ? ? ? ? ? ? ? ?
????????????currentLevel = AudioChannel.thresholdLevel ? ? ? ? ? ?
????????????} ? ? ? ? ? ?
????????????if currentLevel > AudioChannel.maxInputLevelForAllChannels { ? ? ? ? ? ? ? ?
????????????// store this as the new overall maximum input level ? ? ? ? ? ? ? ?
????????????AudioChannel.maxInputLevelForAllChannels = currentLevel? ? ? ? ? ? ?
????????????} ? ? ? ?
????????} ? ?
????}
}
在這兩個檢查的第一個中,didSet觀察者將設(shè)置currentLevel為不同的值溪窒。但是坤塞,這不會導(dǎo)致再次調(diào)用觀察者。