鍍金池/ 教程/ iOS/ 屬性(Properties)
特性(Attributes)
Access Control 權(quán)限控制的黑與白
基本運(yùn)算符(Basic Operators)
基礎(chǔ)部分(The Basics)
閉包(Closures)
擴(kuò)展
泛型參數(shù)(Generic Parameters and Arguments)
訪問(wèn)控制和 protected
語(yǔ)句(Statements)
模式(Patterns)
WWDC 里面的那個(gè)“大炮打氣球”
關(guān)于語(yǔ)言參考(About the Language Reference)
語(yǔ)法總結(jié)(Summary of the Grammar)
嵌套類型
類型(Types)
Swift 初見(jiàn)(A Swift Tour)
泛型
枚舉(Enumerations)
高級(jí)運(yùn)算符
繼承
析構(gòu)過(guò)程
關(guān)于 Swift(About Swift)
訪問(wèn)控制
類和結(jié)構(gòu)體
內(nèi)存安全
Swift 與 C 語(yǔ)言指針友好合作
協(xié)議
屬性(Properties)
可選類型完美解決占位問(wèn)題
錯(cuò)誤處理
字符串和字符(Strings and Characters)
聲明(Declarations)
自動(dòng)引用計(jì)數(shù)
Swift 里的值類型與引用類型
表達(dá)式(Expressions)
Swift 文檔修訂歷史
造個(gè)類型不是夢(mèng)-白話 Swift 類型創(chuàng)建
歡迎使用 Swift
詞法結(jié)構(gòu)(Lexical Structure)
集合類型(Collection Types)
下標(biāo)
方法(Methods)
可選鏈?zhǔn)秸{(diào)用
版本兼容性
類型轉(zhuǎn)換
構(gòu)造過(guò)程
The Swift Programming Language 中文版
函數(shù)(Functions)
Swift 教程
控制流(Control Flow)

屬性(Properties)


1.0 翻譯:shinyzhu 校對(duì):pp-prog yangsiy

2.0 翻譯+校對(duì):yangsiy

2.1 翻譯:buginux 校對(duì):shanks,2015-10-29

2.2 翻譯:saitjr,2016-04-11,SketchK 2016-05-13

3.0.1,shanks,2016-11-12

4.0 校對(duì):kemchenj 2017-09-21

4.1 翻譯+校對(duì):mylittleswift

本頁(yè)包含內(nèi)容:

屬性將值跟特定的類、結(jié)構(gòu)或枚舉關(guān)聯(lián)。存儲(chǔ)屬性存儲(chǔ)常量或變量作為實(shí)例的一部分,而計(jì)算屬性計(jì)算(不是存儲(chǔ))一個(gè)值。計(jì)算屬性可以用于類、結(jié)構(gòu)體和枚舉,存儲(chǔ)屬性只能用于類和結(jié)構(gòu)體。

存儲(chǔ)屬性和計(jì)算屬性通常與特定類型的實(shí)例關(guān)聯(lián)。但是,屬性也可以直接作用于類型本身,這種屬性稱為類型屬性。

另外,還可以定義屬性觀察器來(lái)監(jiān)控屬性值的變化,以此來(lái)觸發(fā)一個(gè)自定義的操作。屬性觀察器可以添加到自己定義的存儲(chǔ)屬性上,也可以添加到從父類繼承的屬性上。

存儲(chǔ)屬性

簡(jiǎn)單來(lái)說(shuō),一個(gè)存儲(chǔ)屬性就是存儲(chǔ)在特定類或結(jié)構(gòu)體實(shí)例里的一個(gè)常量或變量。存儲(chǔ)屬性可以是變量存儲(chǔ)屬性(用關(guān)鍵字 var 定義),也可以是常量存儲(chǔ)屬性(用關(guān)鍵字 let 定義)。

可以在定義存儲(chǔ)屬性的時(shí)候指定默認(rèn)值,請(qǐng)參考默認(rèn)構(gòu)造器一節(jié)。也可以在構(gòu)造過(guò)程中設(shè)置或修改存儲(chǔ)屬性的值,甚至修改常量存儲(chǔ)屬性的值,請(qǐng)參考構(gòu)造過(guò)程中常量屬性的修改一節(jié)。

下面的例子定義了一個(gè)名為 FixedLengthRange 的結(jié)構(gòu)體,該結(jié)構(gòu)體用于描述整數(shù)的范圍,且這個(gè)范圍值在被創(chuàng)建后不能被修改。

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 該區(qū)間表示整數(shù)0,1,2
rangeOfThreeItems.firstValue = 6
// 該區(qū)間現(xiàn)在表示整數(shù)6,7,8

FixedLengthRange 的實(shí)例包含一個(gè)名為 firstValue 的變量存儲(chǔ)屬性和一個(gè)名為 length 的常量存儲(chǔ)屬性。在上面的例子中,length 在創(chuàng)建實(shí)例的時(shí)候被初始化,因?yàn)樗且粋€(gè)常量存儲(chǔ)屬性,所以之后無(wú)法修改它的值。

常量結(jié)構(gòu)體的存儲(chǔ)屬性

如果創(chuàng)建了一個(gè)結(jié)構(gòu)體的實(shí)例并將其賦值給一個(gè)常量,則無(wú)法修改該實(shí)例的任何屬性,即使有屬性被聲明為變量也不行:

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 該區(qū)間表示整數(shù)0,1,2,3
rangeOfFourItems.firstValue = 6
// 盡管 firstValue 是個(gè)變量屬性,這里還是會(huì)報(bào)錯(cuò)

因?yàn)?rangeOfFourItems 被聲明成了常量(用 let 關(guān)鍵字),即使 firstValue 是一個(gè)變量屬性,也無(wú)法再修改它了。

這種行為是由于結(jié)構(gòu)體(struct)屬于值類型。當(dāng)值類型的實(shí)例被聲明為常量的時(shí)候,它的所有屬性也就成了常量。

屬于引用類型的類(class)則不一樣。把一個(gè)引用類型的實(shí)例賦給一個(gè)常量后,仍然可以修改該實(shí)例的變量屬性。

延遲存儲(chǔ)屬性

延遲存儲(chǔ)屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會(huì)計(jì)算其初始值的屬性。在屬性聲明前使用 lazy 來(lái)標(biāo)示一個(gè)延遲存儲(chǔ)屬性。

注意

必須將延遲存儲(chǔ)屬性聲明成變量(使用 var 關(guān)鍵字),因?yàn)閷傩缘某跏贾悼赡茉趯?shí)例構(gòu)造完成之后才會(huì)得到。而常量屬性在構(gòu)造過(guò)程完成之前必須要有初始值,因此無(wú)法聲明成延遲屬性。

延遲屬性很有用,當(dāng)屬性的值依賴于在實(shí)例的構(gòu)造過(guò)程結(jié)束后才會(huì)知道影響值的外部因素時(shí),或者當(dāng)獲得屬性的初始值需要復(fù)雜或大量計(jì)算時(shí),可以只在需要的時(shí)候計(jì)算它。

下面的例子使用了延遲存儲(chǔ)屬性來(lái)避免復(fù)雜類中不必要的初始化。例子中定義了 DataImporterDataManager 兩個(gè)類,下面是部分代碼:

class DataImporter {
    /*
    DataImporter 是一個(gè)負(fù)責(zé)將外部文件中的數(shù)據(jù)導(dǎo)入的類。
    這個(gè)類的初始化會(huì)消耗不少時(shí)間。
    */
    var fileName = "data.txt"
    // 這里會(huì)提供數(shù)據(jù)導(dǎo)入功能
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 這里會(huì)提供數(shù)據(jù)管理功能
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 實(shí)例的 importer 屬性還沒(méi)有被創(chuàng)建

DataManager 類包含一個(gè)名為 data 的存儲(chǔ)屬性,初始值是一個(gè)空的字符串(String)數(shù)組。這里沒(méi)有給出全部代碼,只需知道 DataManager 類的目的是管理和提供對(duì)這個(gè)字符串?dāng)?shù)組的訪問(wèn)即可。

DataManager 的一個(gè)功能是從文件導(dǎo)入數(shù)據(jù)。該功能由 DataImporter 類提供,DataImporter 完成初始化需要消耗不少時(shí)間:因?yàn)樗膶?shí)例在初始化時(shí)可能要打開文件,還要讀取文件內(nèi)容到內(nèi)存。

DataManager 管理數(shù)據(jù)時(shí)也可能不從文件中導(dǎo)入數(shù)據(jù)。所以當(dāng) DataManager 的實(shí)例被創(chuàng)建時(shí),沒(méi)必要?jiǎng)?chuàng)建一個(gè) DataImporter 的實(shí)例,更明智的做法是第一次用到 DataImporter 的時(shí)候才去創(chuàng)建它。

由于使用了 lazyimporter 屬性只有在第一次被訪問(wèn)的時(shí)候才被創(chuàng)建。比如訪問(wèn)它的屬性 fileName 時(shí):

print(manager.importer.fileName)
// DataImporter 實(shí)例的 importer 屬性現(xiàn)在被創(chuàng)建了
// 輸出 "data.txt”

注意

如果一個(gè)被標(biāo)記為 lazy 的屬性在沒(méi)有初始化時(shí)就同時(shí)被多個(gè)線程訪問(wèn),則無(wú)法保證該屬性只會(huì)被初始化一次。

存儲(chǔ)屬性和實(shí)例變量

如果您有過(guò) Objective-C 經(jīng)驗(yàn),應(yīng)該知道 Objective-C 為類實(shí)例存儲(chǔ)值和引用提供兩種方法。除了屬性之外,還可以使用實(shí)例變量作為屬性值的后端存儲(chǔ)。

Swift 編程語(yǔ)言中把這些理論統(tǒng)一用屬性來(lái)實(shí)現(xiàn)。Swift 中的屬性沒(méi)有對(duì)應(yīng)的實(shí)例變量,屬性的后端存儲(chǔ)也無(wú)法直接訪問(wèn)。這就避免了不同場(chǎng)景下訪問(wèn)方式的困擾,同時(shí)也將屬性的定義簡(jiǎn)化成一個(gè)語(yǔ)句。屬性的全部信息——包括命名、類型和內(nèi)存管理特征——作為類型定義的一部分,都定義在一個(gè)地方。

計(jì)算屬性

除存儲(chǔ)屬性外,類、結(jié)構(gòu)體和枚舉可以定義計(jì)算屬性。計(jì)算屬性不直接存儲(chǔ)值,而是提供一個(gè) getter 和一個(gè)可選的 setter,來(lái)間接獲取和設(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))")
// 打印 "square.origin is now at (10.0, 10.0)”

這個(gè)例子定義了 3 個(gè)結(jié)構(gòu)體來(lái)描述幾何形狀:

  • Point 封裝了一個(gè) (x, y) 的坐標(biāo)
  • Size 封裝了一個(gè) width 和一個(gè) height
  • Rect 表示一個(gè)有原點(diǎn)和尺寸的矩形

Rect 也提供了一個(gè)名為 center 的計(jì)算屬性。一個(gè)矩形的中心點(diǎn)可以從原點(diǎn)(origin)和大?。?code>size)算出,所以不需要將它以顯式聲明的 Point 來(lái)保存。Rect 的計(jì)算屬性 center 提供了自定義的 getter 和 setter 來(lái)獲取和設(shè)置矩形的中心點(diǎn),就像它有一個(gè)存儲(chǔ)屬性一樣。

上述例子中創(chuàng)建了一個(gè)名為 squareRect 實(shí)例,初始值原點(diǎn)是 (0, 0),寬度高度都是 10。如下圖中藍(lán)色正方形所示。

squarecenter 屬性可以通過(guò)點(diǎn)運(yùn)算符(square.center)來(lái)訪問(wèn),這會(huì)調(diào)用該屬性的 getter 來(lái)獲取它的值。跟直接返回已經(jīng)存在的值不同,getter 實(shí)際上通過(guò)計(jì)算然后返回一個(gè)新的 Point 來(lái)表示 square 的中心點(diǎn)。如代碼所示,它正確返回了中心點(diǎn) (5, 5)

center 屬性之后被設(shè)置了一個(gè)新的值 (15, 15),表示向右上方移動(dòng)正方形到如下圖橙色正方形所示的位置。設(shè)置屬性 center 的值會(huì)調(diào)用它的 setter 來(lái)修改屬性 originxy 的值,從而實(shí)現(xiàn)移動(dòng)正方形到新的位置。

Computed Properties sample

簡(jiǎn)化 Setter 聲明

如果計(jì)算屬性的 setter 沒(méi)有定義表示新值的參數(shù)名,則可以使用默認(rèn)名稱 newValue。下面是使用了簡(jiǎn)化 setter 聲明的 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)
        }
    }
}

只讀計(jì)算屬性

只有 getter 沒(méi)有 setter 的計(jì)算屬性就是只讀計(jì)算屬性。只讀計(jì)算屬性總是返回一個(gè)值,可以通過(guò)點(diǎn)運(yùn)算符訪問(wèn),但不能設(shè)置新的值。

注意

必須使用 var 關(guān)鍵字定義計(jì)算屬性,包括只讀計(jì)算屬性,因?yàn)樗鼈兊闹挡皇枪潭ǖ摹?code>let 關(guān)鍵字只用來(lái)聲明常量屬性,表示初始化后再也無(wú)法修改的值。

只讀計(jì)算屬性的聲明可以去掉 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)")
// 打印 "the volume of fourByFiveByTwo is 40.0"

這個(gè)例子定義了一個(gè)名為 Cuboid 的結(jié)構(gòu)體,表示三維空間的立方體,包含 widthheightdepth 屬性。結(jié)構(gòu)體還有一個(gè)名為 volume 的只讀計(jì)算屬性用來(lái)返回立方體的體積。為 volume 提供 setter 毫無(wú)意義,因?yàn)闊o(wú)法確定如何修改 width、heightdepth 三者的值來(lái)匹配新的 volume。然而,Cuboid 提供一個(gè)只讀計(jì)算屬性來(lái)讓外部用戶直接獲取體積是很有用的。

屬性觀察器

屬性觀察器監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器,即使新值和當(dāng)前值相同的時(shí)候也不例外。

你可以為除了延遲存儲(chǔ)屬性之外的其他存儲(chǔ)屬性添加屬性觀察器,也可以通過(guò)重寫屬性的方式為繼承的屬性(包括存儲(chǔ)屬性和計(jì)算屬性)添加屬性觀察器。你不必為非重寫的計(jì)算屬性添加屬性觀察器,因?yàn)榭梢酝ㄟ^(guò)它的 setter 直接監(jiān)控和響應(yīng)值的變化。 屬性重寫請(qǐng)參考重寫

可以為屬性添加如下的一個(gè)或全部觀察器:

  • willSet 在新的值被設(shè)置之前調(diào)用
  • didSet 在新的值被設(shè)置之后立即調(diào)用

willSet 觀察器會(huì)將新的屬性值作為常量參數(shù)傳入,在 willSet 的實(shí)現(xiàn)代碼中可以為這個(gè)參數(shù)指定一個(gè)名稱,如果不指定則參數(shù)仍然可用,這時(shí)使用默認(rèn)名稱 newValue 表示。

同樣,didSet 觀察器會(huì)將舊的屬性值作為參數(shù)傳入,可以為該參數(shù)命名或者使用默認(rèn)參數(shù)名 oldValue。如果在 didSet 方法中再次對(duì)該屬性賦值,那么新值會(huì)覆蓋舊的值。

注意

父類的屬性在子類的構(gòu)造器中被賦值時(shí),它在父類中的 willSetdidSet 觀察器會(huì)被調(diào)用,隨后才會(huì)調(diào)用子類的觀察器。在父類初始化方法調(diào)用之前,子類給屬性賦值時(shí),觀察器不會(huì)被調(diào)用。

有關(guān)構(gòu)造器代理的更多信息,請(qǐng)參考值類型的構(gòu)造器代理類的構(gòu)造器代理規(guī)則。

下面是一個(gè) willSetdidSet 實(shí)際運(yùn)用的例子,其中定義了一個(gè)名為 StepCounter 的類,用來(lái)統(tǒng)計(jì)一個(gè)人步行時(shí)的總步數(shù)。這個(gè)類可以跟計(jì)步器或其他日常鍛煉的統(tǒng)計(jì)裝置的輸入數(shù)據(jù)配合使用。

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

StepCounter 類定義了一個(gè) Int 類型的屬性 totalSteps,它是一個(gè)存儲(chǔ)屬性,包含 willSetdidSet 觀察器。

當(dāng) totalSteps 被設(shè)置新值的時(shí)候,它的 willSetdidSet 觀察器都會(huì)被調(diào)用,即使新值和當(dāng)前值完全相同時(shí)也會(huì)被調(diào)用。

例子中的 willSet 觀察器將表示新值的參數(shù)自定義為 newTotalSteps,這個(gè)觀察器只是簡(jiǎn)單的將新的值輸出。

didSet 觀察器在 totalSteps 的值改變后被調(diào)用,它把新值和舊值進(jìn)行對(duì)比,如果總步數(shù)增加了,就輸出一個(gè)消息表示增加了多少步。didSet 沒(méi)有為舊值提供自定義名稱,所以默認(rèn)值 oldValue 表示舊值的參數(shù)名。

注意

如果將屬性通過(guò) in-out 方式傳入函數(shù),willSetdidSet 也會(huì)調(diào)用。這是因?yàn)?in-out 參數(shù)采用了拷入拷出模式:即在函數(shù)內(nèi)部使用的是參數(shù)的 copy,函數(shù)結(jié)束后,又對(duì)參數(shù)重新賦值。關(guān)于 in-out 參數(shù)詳細(xì)的介紹,請(qǐng)參考輸入輸出參數(shù)

全局變量和局部變量

計(jì)算屬性和屬性觀察器所描述的功能也可以用于全局變量局部變量。全局變量是在函數(shù)、方法、閉包或任何類型之外定義的變量。局部變量是在函數(shù)、方法或閉包內(nèi)部定義的變量。

前面章節(jié)提到的全局或局部變量都屬于存儲(chǔ)型變量,跟存儲(chǔ)屬性類似,它為特定類型的值提供存儲(chǔ)空間,并允許讀取和寫入。

另外,在全局或局部范圍都可以定義計(jì)算型變量和為存儲(chǔ)型變量定義觀察器。計(jì)算型變量跟計(jì)算屬性一樣,返回一個(gè)計(jì)算結(jié)果而不是存儲(chǔ)值,聲明格式也完全一樣。

注意

全局的常量或變量都是延遲計(jì)算的,跟延遲存儲(chǔ)屬性相似,不同的地方在于,全局的常量或變量不需要標(biāo)記 lazy 修飾符。

局部范圍的常量或變量從不延遲計(jì)算。

類型屬性

實(shí)例屬性屬于一個(gè)特定類型的實(shí)例,每創(chuàng)建一個(gè)實(shí)例,實(shí)例都擁有屬于自己的一套屬性值,實(shí)例之間的屬性相互獨(dú)立。

也可以為類型本身定義屬性,無(wú)論創(chuàng)建了多少個(gè)該類型的實(shí)例,這些屬性都只有唯一一份。這種屬性就是類型屬性

類型屬性用于定義某個(gè)類型所有實(shí)例共享的數(shù)據(jù),比如所有實(shí)例都能用的一個(gè)常量(就像 C 語(yǔ)言中的靜態(tài)常量),或者所有實(shí)例都能訪問(wèn)的一個(gè)變量(就像 C 語(yǔ)言中的靜態(tài)變量)。

存儲(chǔ)型類型屬性可以是變量或常量,計(jì)算型類型屬性跟實(shí)例的計(jì)算型屬性一樣只能定義成變量屬性。

注意

跟實(shí)例的存儲(chǔ)型屬性不同,必須給存儲(chǔ)型類型屬性指定默認(rèn)值,因?yàn)轭愋捅旧頉](méi)有構(gòu)造器,也就無(wú)法在初始化過(guò)程中使用構(gòu)造器給類型屬性賦值。

存儲(chǔ)型類型屬性是延遲初始化的,它們只有在第一次被訪問(wèn)的時(shí)候才會(huì)被初始化。即使它們被多個(gè)線程同時(shí)訪問(wèn),系統(tǒng)也保證只會(huì)對(duì)其進(jìn)行一次初始化,并且不需要對(duì)其使用 lazy 修飾符。

類型屬性語(yǔ)法

在 C 或 Objective-C 中,與某個(gè)類型關(guān)聯(lián)的靜態(tài)常量和靜態(tài)變量,是作為全局(global)靜態(tài)變量定義的。但是在 Swift 中,類型屬性是作為類型定義的一部分寫在類型最外層的花括號(hào)內(nèi),因此它的作用范圍也就在類型支持的范圍內(nèi)。

使用關(guān)鍵字 static 來(lái)定義類型屬性。在為類定義計(jì)算型類型屬性時(shí),可以改用關(guān)鍵字 class 來(lái)支持子類對(duì)父類的實(shí)現(xiàn)進(jìn)行重寫。下面的例子演示了存儲(chǔ)型和計(jì)算型類型屬性的語(yǔ)法:

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í)例屬性的語(yǔ)法相同。

獲取和設(shè)置類型屬性的值

跟實(shí)例屬性一樣,類型屬性也是通過(guò)點(diǎn)運(yùn)算符來(lái)訪問(wèn)。但是,類型屬性是通過(guò)類型本身來(lái)訪問(wèn),而不是通過(guò)實(shí)例。比如:

print(SomeStructure.storedTypeProperty)
// 打印 "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印 "Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印 "6"
print(SomeClass.computedTypeProperty)
// 打印 "27"

下面的例子定義了一個(gè)結(jié)構(gòu)體,使用兩個(gè)存儲(chǔ)型類型屬性來(lái)表示兩個(gè)聲道的音量,每個(gè)聲道具有 010 之間的整數(shù)音量。

下圖展示了如何把兩個(gè)聲道結(jié)合來(lái)模擬立體聲的音量。當(dāng)聲道的音量是 0,沒(méi)有一個(gè)燈會(huì)亮;當(dāng)聲道的音量是 10,所有燈點(diǎn)亮。本圖中,左聲道的音量是 9,右聲道的音量是 7

Static Properties VUMeter

上面所描述的聲道模型使用 AudioChannel 結(jié)構(gòu)體的實(shí)例來(lái)表示:

struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // 將當(dāng)前音量限制在閾值之內(nèi)
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // 存儲(chǔ)當(dāng)前音量作為新的最大輸入音量
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}

結(jié)構(gòu) AudioChannel 定義了 2 個(gè)存儲(chǔ)型類型屬性來(lái)實(shí)現(xiàn)上述功能。第一個(gè)是 thresholdLevel,表示音量的最大上限閾值,它是一個(gè)值為 10 的常量,對(duì)所有實(shí)例都可見(jiàn),如果音量高于 10,則取最大上限值 10(見(jiàn)后面描述)。

第二個(gè)類型屬性是變量存儲(chǔ)型屬性 maxInputLevelForAllChannels,它用來(lái)表示所有 AudioChannel 實(shí)例的最大音量,初始值是 0

AudioChannel 也定義了一個(gè)名為 currentLevel 的存儲(chǔ)型實(shí)例屬性,表示當(dāng)前聲道現(xiàn)在的音量,取值為 010。

屬性 currentLevel 包含 didSet 屬性觀察器來(lái)檢查每次設(shè)置后的屬性值,它做如下兩個(gè)檢查:

  • 如果 currentLevel 的新值大于允許的閾值 thresholdLevel,屬性觀察器將 currentLevel 的值限定為閾值 thresholdLevel。
  • 如果修正后的 currentLevel 值大于靜態(tài)類型屬性 maxInputLevelForAllChannels 的值,屬性觀察器就將新值保存在 maxInputLevelForAllChannels 中。

注意

在第一個(gè)檢查過(guò)程中,didSet 屬性觀察器將 currentLevel 設(shè)置成了不同的值,但這不會(huì)造成屬性觀察器被再次調(diào)用。

可以使用結(jié)構(gòu)體 AudioChannel 創(chuàng)建兩個(gè)聲道 leftChannelrightChannel,用以表示立體聲系統(tǒng)的音量:

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

如果將左聲道的 currentLevel 設(shè)置成 7,類型屬性 maxInputLevelForAllChannels 也會(huì)更新成 7

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// 輸出 "7"
print(AudioChannel.maxInputLevelForAllChannels)
// 輸出 "7"

如果試圖將右聲道的 currentLevel 設(shè)置成 11,它會(huì)被修正到最大值 10,同時(shí) maxInputLevelForAllChannels 的值也會(huì)更新到 10

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// 輸出 "10"
print(AudioChannel.maxInputLevelForAllChannels)
// 輸出 "10"