Swift5.x-屬性(中文文檔)

引言

繼續學習Swift文檔,從上一章節:結構體和類,咱們學習了Swift結構體和類相關的內容,如結構體和類的定義和使用、值類型和引用類型的區別、「===」和「!==」符號判斷類的實例是否相同等這些內容。如今,咱們學習Swift的屬性相關的內容。因爲篇幅較長,這裏分篇來記錄,接下來,Fighting!html

若是你已經掌握這一章節內容,請移步下一章節:方法數據庫

屬性

屬性將值與特定的類,結構體或枚舉關聯。 存儲的屬性將常量和變量值存儲爲實例的一部分,而計算的屬性將計算(而不是存儲)值。 計算的屬性由類,結構體和枚舉提供。 存儲的屬性僅由類和結構體提供。swift

存儲和計算的屬性一般與特定類型的實例相關聯。 可是,屬性也能夠與類型自己關聯。 這樣的屬性稱爲類屬性。數組

此外,您能夠定義屬性觀察者以監聽屬性值的變化,您可使用自定義操做對其進行響應。 能夠將屬性觀察器添加到您本身定義的存儲屬性中,也能夠添加到子類從其超類繼承的屬性中。安全

您還可使用屬性包裝器在多個屬性的getter和setter中重用代碼。bash

1 存儲屬性

最簡單的形式是,存儲的屬性是做爲特定類或結構體的實例的一部分存儲的常量或變量。 存儲屬性能夠是可變存儲屬性(由var關鍵字引入)或恆定存儲屬性(由let關鍵字引入)。閉包

您能夠爲存儲屬性提供默認值做爲其定義的一部分,如Default Property Values中所述。 您還能夠在初始化期間設置和修改存儲屬性的初始值。 即便對於常量存儲的屬性也是如此,如Assigning Constant Properties During Initialization中所述。app

下面的示例定義了一個稱爲FixedLengthRange的結構體,該結構體描述了一個整數範圍,該整數範圍的長度在建立後不能更改:ide

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的常數存儲屬性。 在上面的示例中,length是在建立新範圍時初始化的,此後不能更改,由於它是常量屬性。函數

1.1 常量結構體實例的存儲屬性

若是您建立結構體的實例並將該實例分配給常量,則即便它們被聲明爲變量屬性,也沒法修改實例的屬性:

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關鍵字),因此即便firstValue是變量屬性,也沒法更改其firstValue屬性。

此行爲是因爲結構體是值類型。 當值類型的實例標記爲常量時,其全部屬性也都標記爲常量。

對於類(引用類型)而言,狀況並不是如此。 若是您將引用類型的實例分配給常量,則仍然能夠更改該實例的變量屬性。

1.2 懶加載存儲屬性

懶加載存儲屬性是其首次使用以前不會計算其初始值的屬性。 您能夠經過在聲明以前編寫lazy修飾符來表示一個懶加載存儲屬性。

注意
您必須始終將懶加載屬性聲明爲變量(使用var關鍵字),由於直到實例初始化完成後纔可能檢索其初始值。 常量屬性在初始化完成以前必須始終具備一個值,所以不能聲明爲lazy的。

當屬性的初始值取決於實例初始化完成後才知道其值的外部因素時,lazy屬性頗有用。 當屬性的初始值須要複雜或計算量大的設置(除非或直到須要時才執行)時,lazy屬性也頗有用。

下面的示例使用lazy存儲的屬性,以免沒必要要的複雜類的初始化。 本示例定義了兩個名爲DataImporter和DataManager的類,兩個類均未完整顯示:

class DataImporter {
    /*
    DataImporter is a class to import data from an external file.
    The class is assumed to take a nontrivial 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類具備一個稱爲data的存儲屬性,該屬性使用新的空String數組初始化。 儘管未顯示其其他功能,但此DataManager類的目的是管理並提供對此String數據數組的訪問。

DataManager類的功能的一部分是從文件導入數據的能力。 此功能由DataImporter類提供,假定它花費了很短的時間來初始化。 這多是由於在初始化DataImporter實例時,DataImporter實例須要打開文件並將其內容讀入內存。

DataManager實例能夠在不從文件導入數據的狀況下管理其數據,所以在建立DataManager自己時無需建立新的DataImporter實例。 相反,若是首次使用DataImporter實例,則在建立它時更有意義。

由於它被標記爲lazy修飾符,因此僅在首次訪問importer屬性時(例如,在查詢其filename屬性時)才爲importer屬性建立DataImporter實例:

print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
複製代碼

注意
若是標記有lazy修飾符的屬性同時被多個線程訪問,而且該屬性還沒有初始化,則不能保證該屬性只能被初始化一次。

1.3 存儲屬性和實例變量

若是您有使用Objective-C的經驗,您可能會知道它提供了兩種將值和引用存儲爲類實例的一部分的方法。 除了屬性以外,您還能夠將實例變量用做存儲在屬性中的值的後備存儲。

Swift將這些概念統一爲一個屬性聲明。 Swift屬性沒有相應的實例變量,而且不能直接訪問該屬性的後備存儲。 這種方法避免了在不一樣上下文中如何訪問值的困惑,並將屬性的聲明簡化爲單個肯定的語句。 有關該屬性的全部信息(包括其名稱,類型和內存管理特徵)都在單個位置中定義,做爲類型定義的一部分。

2 計算屬性

除了存儲的屬性外,類,結構和枚舉還能夠定義計算的屬性,這些屬性實際上並不存儲值。 相反,它們提供了一個getter和一個可選的setter,以間接檢索和設置其餘屬性和值。

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)"
複製代碼

本示例定義了用於處理幾何形狀的三種結構:

  • Point封裝點的x和y座標。
  • Size封裝了寬度和高度。
  • Rect經過原點和大小定義一個矩形。

Rect結構還提供了一個稱爲center的計算屬性。 Rect的當前中心位置始終能夠根據其原點和大小肯定,所以您無需將中心點存儲爲明確的Point值。 取而代之的是,Rect爲一個稱爲center的計算變量定義了一個自定義的getter和setter方法,以使您可以像處理真正的存儲屬性同樣使用矩形的center。

上面的示例建立了一個新的Rect變量,稱爲square。 使用起始點(0,0)以及寬度和高度10初始化square變量。該正方形在下圖中由藍色正方形表示。

而後,經過點語法(square.center)訪問square變量的center屬性,這將致使調用center的getter來檢索當前屬性值。 getter實際上不是返回現有值,而是計算並返回一個新的Point來表示正方形的中心。 從上面能夠看到,正確返回中心點(5,5)。

而後將center屬性設置爲新值(15,15),該值將正方形向上和向右移動到下圖中橙色正方形所示的新位置。 設置center屬性會調用setter做爲center,它會修改存儲的原點屬性的x和y值,並將正方形移至新位置。

2.1 簡寫setter聲明

若是計算屬性的setter未定義要設置的新值的名稱,則使用默認名稱newValue。 這是Rect結構體的替代版本,它利用了這種簡寫形式:

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)
        }
    }
}
複製代碼

2.2 簡寫getter聲明

若是getter的整個主體是單個表達式,則getter隱式返回該表達式。 這是Rect結構體的另外一個版本,它利用了該簡寫和setter的簡寫:

struct CompactRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}
複製代碼

忽略getter返回的操做遵循與忽略函數返回的操做規則相同,如帶Functions With an Implicit Return中所述。

2.3 只讀計算屬性

具備getter但沒有setter的計算屬性稱爲只讀計算屬性。 只讀的計算屬性始終返回一個值,而且能夠經過點語法進行訪問,但不能將其設置爲其餘值。

注意
您必須使用var關鍵字將計算屬性(包括只讀計算屬性)聲明爲變量屬性,由於它們的值是固定的。 let關鍵字僅用於常量屬性,以指示一旦將它們的值設置爲實例初始化的一部分就沒法更改它們的值。

您能夠經過刪除get關鍵字及其花括號來簡化對只讀計算屬性的聲明:

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"
複製代碼

本示例定義了一個稱爲Cuboid的新結構,該結構表示具備width,height和depth屬性的3D矩形框。 此結構還具備一個稱爲volume的只讀計算屬性,該屬性計算並返回長方體的當前體積。 可設置的體積沒有意義,由於對於特定的體積值應該使用哪一個寬度,高度和深度值是不明確的。 可是,對於長方體提供只讀的計算屬性以使外部用戶可以發現其當前的計算量很是有用。

3 屬性監聽

屬性監聽觀察並響應屬性值的變化。 每次設置屬性值時都會調用屬性監聽,即便新值與屬性的當前值相同也是如此。

您能夠在如下位置添加屬性監聽:

  • 定義的存儲屬性
  • 繼承的存儲屬性
  • 繼承的計算屬性

對於繼承的屬性,能夠經過在子類中覆蓋該屬性來添加屬性監聽。 對於您定義的計算屬性,請使用屬性的setter來觀察並響應值更改,而不是嘗試建立觀察者。 覆蓋屬性在Overriding中描述。

您能夠選擇在屬性上定義這些觀察者之一或所有:

  • 將在存儲值以前調用willSet。
  • 存儲新值後,將當即調用didSet。

若是您實現了willSet觀察者,則會將新的屬性值做爲常量參數傳遞。 您能夠在willSet實現中爲該參數指定一個名稱。 若是您未在實現中寫入參數名稱和括號,則該參數將用默認參數名稱newValue。

一樣,若是您實現了didSet觀察者,則會傳遞一個包含舊屬性值的常量參數。 您能夠命名參數或使用默認參數名稱oldValue。 若是您在本身的didSet觀察者中爲屬性分配值,則分配的新值將替換剛剛設置的值。

注意
在調用父類初始化器以後,在子類初始化器中設置屬性時,將調用父類屬性的willSet和didSet觀察器。 在類設置其本身的屬性時,在調用父類初始化程序以前,不會調用它們。

有關初始化程序委託的更多信息,請參見Initializer Delegation for Value Types 和 Initializer Delegation for Class Types

這是willSet和didSet實際使用的示例。 下面的示例定義了一個稱爲StepCounter的新類,該類跟蹤一我的在行走時所走的總步數。 此類能夠與計步器或其餘計步器的輸入數據一塊兒使用,以跟蹤一我的的平常活動。

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
var stepCounter = StepCounter()  //這裏官方文檔上用的let,Xcode會報錯
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類聲明瞭Int類型的totalSteps屬性。 這是帶有willSet和didSet觀察者的存儲屬性。

每當爲屬性分配新值時,都會調用totalSteps的willSet和didSet觀察器。 即便新值與當前值相同,也是如此。

此示例的willSet觀察者使用自定義參數名稱newTotalSteps做爲即將到來的新值。 在此示例中,它僅打印出將要設置的值。

在更新totalSteps的值以後,將調用didSet觀察器。 它將totalSteps的新值與舊值進行比較。 若是步驟總數增長,則會顯示一條消息,指示已執行了多少個新步驟。 didSet觀察器沒有爲舊值提供自定義參數名稱,而是使用了默認名稱oldValue。

注意
若是將具備觀察者的屬性做爲in-out參數傳遞給函數,則將始終會調用willSet和didSet觀察者。 這是由於in-out參數的copy-in、 copy-out內存模型:該值始終在函數結尾設置到該屬性上。 有關in-out參數行爲的詳細討論,請參見 In-Out Parameters

4 屬性包裝

屬性包裝器在管理屬性存儲方式的代碼與定義屬性的代碼之間增長了一層隔離。 例如,若是您具備提供線程安全檢查或將其基礎數據存儲在數據庫中的屬性,則必須在每一個屬性上編寫該代碼。 使用屬性包裝器時,定義包裝器時,只需編寫一次管理代碼,而後經過將其應用於多個屬性來重用該管理代碼。

要定義屬性包裝器,您須要建立一個結構體,枚舉或類來定義wrappedValue屬性。 在下面的代碼中,TwelveOrLess結構體確保包裝的值始終包含小於或等於12的數字。若是您要求存儲更大的數字,則改成存儲12。

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    init() { self.number = 0 }
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}
複製代碼

setter確保新值小於12,而且getter返回存儲的值。

注意
上面示例中的number聲明將變量標記爲private,這確保了number僅在TwelveOrLess的實現中使用。 編寫在其餘任何地方的代碼都使用wrappedValue的getter和setter來訪問值,而且不能直接使用數字。 有關私有的信息,請參閱Access Control

經過將包裝器的名稱寫爲屬性,將包裝器的名稱應用於屬性。 這是一個存儲小矩形的結構,使用的定義與TwelveOrLess屬性封裝器所實現的「 small」相同(至關隨意):

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var r = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12"
複製代碼

height和width屬性從TwelveOrLess的定義獲取其初始值,該定義將TwelveOrLess.number設置爲零。 將數字10存儲到矩形中。成功完成此操做是由於數字很小。 嘗試存儲24實際上存儲的是12的值,由於24對於屬性設置者的規則來講太大了。

當您將包裝器應用於屬性時,編譯器會合成爲包裝器提供存儲的代碼和提供經過包裝器訪問屬性的代碼。 (屬性包裝器負責存儲包裝後的值,所以沒有synthesized的代碼。)您能夠編寫使用屬性包裝器行爲的代碼,而無需利用特殊的屬性語法。 例如,這是上一個代碼清單中的SmallRectangle版本,該版本將其屬性明確地包裝在TwelveOrLess結構中,而不是將@TwelveOrLess編寫爲屬性:

struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}
複製代碼

_height和_width屬性存儲屬性包裝器TwelveOrLess的實例。 高度和寬度的getter和setter包裝對wrappedValue屬性的訪問。

4.1 設置包裝屬性的初始值

上面示例中的代碼經過在TwelveOrLess的定義中給數字一個初始值來設置包裝屬性的初始值。 使用此屬性包裝器的代碼沒法爲TwelveOrLess所包裝的屬性指定其餘初始值,例如,SmallRectangle的定義沒法提供高度或寬度的初始值。 爲了支持設置初始值或其餘自定義,屬性包裝器須要添加一個初始化程序。 這是TwelveOrLess的擴展版本,稱爲SmallNumber,它定義了設置包裝值和最大值的初始化程序:

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }

    init() {
        maximum = 12
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}
複製代碼

SmallNumber的定義包括三個初始值設定項:init(),init(wrappedValue :)和init(wrappedValue:maximum :),下面的示例用於設置包裹值和最大值。 有關初始化和初始化程序語法的信息,請參見Initialization

當您將包裝器應用於屬性時,若是您未指定初始值,則Swift會使用init()初始化程序來設置包裝器。 例如:

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
複製代碼

包裹height和width的SmallNumber實例是經過調用SmallNumber()建立的。 初始化程序中的代碼使用默認值零和12設置初始包裝值和初始最大值。屬性包裝器仍然提供全部初始值,就像以前在SmallRectangle中使用TwelveOrLess的示例同樣。

與該示例不一樣,SmallNumber還支持編寫這些初始值做爲聲明屬性的一部分。爲屬性指定初始值時,Swift使用init(wrappedValue :)初始化程序來設置包裝器。 例如:

struct UnitRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"
複製代碼

當您在具備包裝器的屬性上寫入= 1時,該值將轉換爲對init(wrappedValue :)初始化程序的調用。 包裹高度和寬度的SmallNumber實例是經過調用SmallNumber(wrappedValue:1)建立的。 初始化程序使用此處指定的換行值,並使用默認的最大值12。

當您在自定義屬性後的括號中寫入參數時,Swift將使用接受這些參數的初始化程序來設置包裝器。 例如,若是您提供一個初始值和一個最大值,Swift將使用init(wrappedValue:maximum :)初始化程序:

struct NarrowRectangle {
    @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
    @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}

var narrowRectangle = NarrowRectangle()
1
// Prints "2 3"

narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"
複製代碼

包裹高度的SmallNumber實例是經過調用SmallNumber(wrappedValue:2,最大值:5)建立的,包裹寬度的實例是經過調用SmallNumber(wrappedValue:3,最大值:4)建立的。

經過包含屬性包裝器的參數,您能夠在包裝器中設置初始狀態,或在建立包裝器時將其餘選項傳遞給包裝器。 此語法是使用屬性包裝器的最通用方法。 您能夠爲屬性提供所需的任何參數,而後將它們傳遞給初始化程序。

當包含屬性包裝器參數時,還可使用賦值指定初始值。 Swift將分配視爲包裝值參數,並使用接受您包含的參數的初始化程序。 例如:

struct MixedRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber(maximum: 9) var width: Int = 2
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"

mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"
複製代碼

包裹高度的SmallNumber實例是經過調用SmallNumber(wrappedValue:1)建立的,該實例使用默認的最大值12。包裹寬度的實例是經過調用SmallNumber(wrappedValue:2,最大值:9)建立的。

4.2 從屬性包裝器投影值

除了包裝的值以外,屬性包裝器還能夠經過定義投影值來公開其餘功能,例如,管理對數據庫的訪問的屬性包裝器能夠在其投影值上公開flushDatabaseConnection()方法。 預計值的名稱與包裝值相同,不一樣之處在於它以美圓符號()開頭。 因爲您的代碼沒法定義以開頭的屬性,所以投影值永遠不會干擾您定義的屬性。

在上面的SmallNumber示例中,若是您嘗試將屬性設置爲太大的數字,則屬性包裝器會在存儲該數字以前對其進行調整。 下面的代碼將一個projectedValue屬性添加到SmallNumber結構中,以跟蹤存儲屬性的包裝器在存儲新值以前是否調整了該新值。

@propertyWrapper
struct SmallNumber {
    private var number: Int
    var projectedValue: Bool
    init() {
        self.number = 0
        self.projectedValue = false
    }
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
}
struct SomeStructure {
    @SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()

someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"
複製代碼

編寫someStructure.someNumber可訪問包裝器的投影值。在存儲了一個較小的數字(如4)以後,someStructure. someNumber的值爲false。可是,在嘗試存儲太大的數字(例如55)後,投影值纔是true。

屬性包裝器能夠返回任何類型的值做爲其投影值。在此示例中,屬性包裝器僅公開一條信息(不管數字是否已調整),所以它公開該布爾值做爲其投影值。須要公開更多信息的包裝器能夠返回某個其餘數據類型的實例,或者能夠返回self以將包裝器的實例做爲其投影值公開。

當您從屬於該類型一部分的代碼(例如屬性獲取器或實例方法)訪問投影值時,能夠省略self。屬性名稱以前,就像訪問其餘屬性同樣。如下示例中的代碼將包裝器在高度和寬度周圍的投影值稱爲$height和$width:

enum Size {
    case small, large
}

struct SizedRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int

    mutating func  (to size: Size) -> Bool {
        switch size {
        case .small:
            height = 10
            width = 20
        case .large:
            height = 100
            width = 100
        }
        return $height || $width
    }
}
複製代碼

由於屬性包裝器語法只是具備getter和setter的屬性的語法糖,因此訪問height和width的行爲與訪問任何其餘屬性的行爲相同。 例如,resize(to :)中的代碼使用其屬性包裝器訪問高度和寬度。 若是調用resize(to:.large),則.large的switch case會將矩形的高度和寬度設置爲100。包裝器將防止這些屬性的值大於12,並將投影值設置爲true,以 記錄它調整其值的事實。 在resize(to :)的結尾,return語句檢查height和 width以肯定屬性包裝器是否調整了高度或寬度。

5 全局和局部變量

上面描述的用於計算和觀察屬性的功能也可用於全局變量和局部變量。 全局變量是在任何函數,方法,閉包或類型上下文以外定義的變量。 局部變量是在函數,方法或閉包上下文中定義的變量。

您在上一章中遇到的全局變量和局部變量都已存儲。 與存儲的屬性同樣,存儲的變量爲特定類型的值提供存儲,並容許設置和檢索該值。

可是,您還能夠在全局或局部範圍內定義計算變量併爲存儲的變量定義觀察者。 計算變量將計算其值,而不是存儲它的值,而且它們的寫法與計算屬性的寫法相同。

注意
全局常量和變量老是以與懶加載存儲屬性相似的方式延遲計算。 與懶加載存儲的屬性不一樣,全局常量和變量不須要使用lazy修飾符進行標記。局部常量和變量毫不會延遲計算。

6 類屬性

實例屬性是屬於特定類型的實例的屬性。 每次建立該類型的新實例時,它都有本身的屬性值集,與其餘任何實例分開。

您還能夠定義屬於類型自己的屬性,而不是屬於該類型的任何一個實例的屬性。 不管您建立了多少個該類型的實例,這些屬性將永遠只有一個副本。 這些類型的屬性稱爲類屬性。

類屬性對於定義特定類型的全部實例通用的值頗有用,例如,全部實例可使用的常量屬性(例如C中的靜態常量),或存儲對全部實例都是全局值的變量屬性。 該類型的實例(例如C中的靜態變量)。

存儲的類屬性能夠是變量或常量。 計算類屬性始終以與計算實例屬性相同的方式聲明爲變量屬性。

注意
與存儲實例屬性不一樣,必須始終爲存儲型屬性賦予默認值。 這是由於類型自己沒有初始化程序,該初始化程序能夠在初始化時將值分配給存儲的type屬性。

存儲的類屬性在其第一次訪問時被延遲初始化。 即便它們同時被多個線程訪問,也只能將它們初始化一次,而且不須要用lazy修飾符進行標記。

6.1 類屬性語法

在C和Objective-C中,將靜態常量和與類型關聯的變量定義爲全局靜態變量。 可是,在Swift中,類屬性被寫爲類型定義的一部分,位於類型的外部花括號內,每一個類屬性都明確地限定爲其支持的類型。

您可使用static關鍵字定義類屬性。 對於類類型的計算類屬性,您能夠改用class關鍵字來容許子類覆蓋超類的實現。 下面的示例顯示了存儲和計算的類屬性的語法:

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var  : 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
    }
}
複製代碼

注意
上面的計算類屬性示例僅適用於只讀計算型屬性,可是您也可使用與計算實例屬性相同的語法定義可讀可寫的計算類屬性。

6.2 查詢和設置類屬性

與實例屬性同樣,查詢類屬性使用點語法來查詢值。 可是,類屬性使用點語法進行設置值,而不是在該類型的實例上進行設置。 例如:

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"
複製代碼

如下示例使用兩個存儲的類屬性做爲爲多個音頻通道的音頻電平表建模的結構的一部分。 每一個通道的音頻電平都在0到10之間(含0和10)。

下圖說明了如何將這些音頻通道中的兩個進行組合以模擬立體聲音頻電平表。 當某個通道的音頻電平爲0時,該通道的全部燈均不點亮。 音頻級別爲10時,該通道的全部指示燈均點亮。 在此圖中,左聲道的當前電平爲9,右聲道的當前電平爲7:

上述音頻通道由AudioChannel結構體的實例表示:

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
            }
        }
    }
}
複製代碼

AudioChannel結構體定義了兩個存儲的類屬性以支持其功能。 第一個thresholdLevel定義了音頻級別能夠採用的最大閾值。 對於全部AudioChannel實例,此常數均爲10。 若是音頻信號的值大於10,則將其設置爲該閾值(以下所述)。

第二個type屬性是一個名爲maxInputLevelForAllChannels的變量存儲屬性。 這將跟蹤任何AudioChannel實例已接收到的最大輸入值。 它以初始值0開始。

AudioChannel結構體還定義了一個名稱爲currentLevel的存儲實例屬性,該屬性表示通道的當前音頻級別,範圍爲0到10。

currentLevel屬性具備didSet屬性觀察器,用於在設置currentLevel時檢查其值。 該觀察者執行兩項檢查:

  • 若是currentLevel的新值大於容許的thresholdLevel,則屬性觀察器將currentLevel限制爲thresholdLevel。
  • 若是currentLevel的新值(在任何上限以後)高於任何AudioChannel實例先前接收的任何值,則屬性觀察器會將新的currentLevel值存儲在maxInputLevelForAllChannels類屬性中。

注意
在這兩個檢查的第一個中,didSet觀察器將currentLevel設置爲不一樣的值。 可是,這不會致使再次調用觀察者。

您可使用AudioChannel結構體建立兩個新的音頻通道,分別稱爲leftChannel和rightChannel,以表示立體聲系統的音頻級別:

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
複製代碼

若是將左通道的currentLevel設置爲7,則能夠看到maxInputLevelForAllChannels類屬性已更新爲等於7:

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
複製代碼

若是您嘗試將右側通道的currentLevel設置爲11,則能夠看到右側通道的currentLevel屬性的最大值設置爲10,而且maxInputLevelForAllChannels類屬性更新爲等於10:

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"
複製代碼

總結

經過這一章節內容,咱們知道了:

  • 存儲的屬性是做爲特定類或結構體的實例的一部分存儲的常量或變量。
  • 結構體是值類型。 當值類型的實例標記爲常量時,其全部屬性也都標記爲常量。 對於類(引用類型)而言,狀況並不是如此。 若是您將引用類型的實例分配給常量,則仍然能夠更改該實例的變量屬性。
  • 懶加載存儲屬性是其首次使用以前不會計算其初始值的屬性。 經過在聲明以前編寫lazy修飾符來表示一個懶加載存儲屬性。
  • 只有變量能夠設置lazy屬性,常量屬性在初始化完成以前必須始終具備一個值,所以不能設置lazy。
  • 除了存儲的屬性外,類,結構和枚舉還能夠定義計算的屬性,這些屬性實際上並不存儲值。 相反,它們提供了一個getter和一個可選的setter,以間接檢索和設置其餘屬性和值。
  • 能夠簡寫setter和getter聲明,用newValue默認名稱。
  • 只讀計算屬性能夠省略get和花括號,直接return值。
  • 屬性監聽:willSet和didSet
  • 將在存儲值以前調用willSet; 存儲新值後,將當即調用didSet。
  • 屬性包裝:@propertyWrapper關鍵詞。 使用屬性包裝器和定義包裝器時,只需編寫一次管理代碼,而後經過將其應用於多個屬性來重用該管理代碼。
  • 類屬性的語法,能夠用static定義類屬性。

這一章節內容結束了,有收穫的朋友動動手指點個👍哦!

上一章節:結構體和類

下一章節:方法

參考文檔:Swift - Properties

相關文章
相關標籤/搜索