【Swift學習】Swift編程之旅---屬性(十四)

  屬性關聯特定類、結構或枚舉的值,存儲屬性將存儲常量和變量做爲實例的一部分,計算屬性用於計算一個值,而不進行存儲。計算屬性能夠用於類、結構體和枚舉裏,存儲屬性只能用於類和結構體。存儲屬性和計算屬性一般用於特定類型的實例,可是,屬性也能夠直接用於類型自己,這種屬性稱爲類型屬性。另外,還能夠定義屬性監視器來觀察屬性值的變化,以此來觸發一個自定義的操做。屬性監視器能夠添加到存儲屬性上,也能夠添加到從父類繼承的屬性。數組

 

  Stored Properties存儲屬性app

   存儲屬性能夠是常量或變量,你能夠給存儲屬性設置默認值也能夠在構造過程當中設置或修改存儲屬性的值,甚至修改常量存儲屬性的值ide

struct FixedLengthRange { 
    var firstValue: Int 
    let length: Int 
} 
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3) 

rangeOfThreeItems.firstValue = 6 

FixedLengthRange的實例包含一個名爲firstValue的變量存儲屬性和一個名爲length的常量存儲屬性。在上面的例子中,length在建立實例的時候被賦值,由於它是一個常量存儲屬性,因此以後沒法修改它的值。this

 
   常量結構體的存儲屬性
  
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聲明成了常量,即便firstValue是一個變量屬性,也沒法再修改它了。這是因爲結構體(struct)屬於值類型。當值類型的實例被聲明爲常量的時候,它的全部屬性也就成了常量。屬於引用類型的類(class)則不同,把一個引用類型的實例賦給一個常量後,仍然能夠修改實例的變量屬性。spa

 

  延遲存儲屬性3d

延遲存儲屬性是指當第一次被調用的時候纔會計算其初始值的屬性。在屬性聲明前使用lazy關鍵字來申明一個延遲存儲屬性。  code

注意:必須將延遲存儲屬性聲明成變量(使用var關鍵字),由於屬性的值在實例構造完成以前可能沒法獲得。而常量屬性在構造過程完成以前必需要有初始值,所以沒法聲明成延遲屬性。blog

延遲屬性頗有用,當屬性的值依賴於在實例的構造過程結束前沒法知道具體值的外部因素時,或者當屬性的值須要複雜或大量計算時,能夠只在須要的時候來計算它。
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類包含一個名爲data的存儲屬性,初始值是一個空的字符串(String)數組。DataManager類的目的是管理和提供對這個字符串數組的訪問。繼承

DataManager的一個功能是從文件導入數據,該功能由DataImporter類提供,DataImporter須要消耗很多時間完成初始化:由於它的實例在初始化時可能要打開文件,還要讀取文件內容到內存。
DataManager也可能不從文件中導入數據。因此當DataManager的實例被建立時,不必當即建立一個DataImporter的實例,當用到DataImporter的時候纔去建立它。

      

  計算屬性內存

計算屬性不直接存儲值,而是提供一個 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) 
println("square.origin is now at (\(square.origin.x), \(square.origin.y))") 
// 輸出 "square.origin is now at (10.0, 10.0)」 

以上定義了 3 個幾何形狀的結構體

Point封裝了一個(x, y)的座標

Size封裝了一個width和height
Rect表示一個有原點和尺寸的矩形

Rect也提供了一個名爲center的計算屬性。一個矩形的中心點能夠從原點和尺寸來算出,因此不須要將它以顯式聲明的Point來保存。Rect的計算屬性center提供了自定義的 getter 和 setter 來獲取和設置矩形的中心點,就像它有一個存儲屬性同樣。

 
例子中接下來建立了一個名爲square的Rect實例,初始值原點是(0, 0),寬度高度都是10。如圖所示藍色正方形。
 
square的center屬性能夠經過點運算符(square.center)來訪問,這會調用 getter 來獲取屬性的值。跟直接返回已經存在的值不一樣,getter 實際上經過計算而後返回一個新的Point來表示square的中心點。如代碼所示,它正確返回了中心點(5, 5)。
 
center屬性以後被設置了一個新的值(15, 15),表示向右上方移動正方形到如圖所示橙色正方形的位置。設置屬性center的值會調用 setter 來修改屬性origin的x和y的值,從而實現移動正方形到新的位置。

 

  簡單setter聲明

    若是計算屬性的 setter 沒有定義表示新值的參數名,則可使用默認名稱newValue。下面是使用了便捷 setter 聲明的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) 
    } 
    } 
} 

 

 

  只讀計算屬性

只有 getter 沒有 setter 的計算屬性就是隻讀計算屬性。只讀計算屬性老是返回一個值,能夠經過點運算符訪問,但不能設置新的值。只讀計算屬性的聲明能夠去掉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) 
println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)") 
// 輸出 "the volume of fourByFiveByTwo is 40.0" 

 

 

  屬性觀察

  觀察屬性值的變化,當發生變化時實現自定義操做。能夠爲屬性添加以下的一個或所有監視器:

  • willSet在屬性值變化前調用
  • didSet在屬性值變化後當即調用

willSet監視器會將新的屬性值做爲固定參數傳入,在willSet的實現代碼中能夠爲這個參數指定一個名稱,若是不指定則參數仍然可用,這時使用默認名稱newValue表示。

似地,didSet監視器會將舊的屬性值做爲參數傳入,能夠爲該參數命名或者使用默認參數名oldValue。

 注意:willSet和didSet監視器在屬性初始化過程當中不會被調用,他們只會當屬性的值在初始化以外的地方被設置時被調用。

 

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類定義了一個Int類型的屬性totalSteps,它是一個存儲屬性,包含willSet和didSet監視器。

當totalSteps設置新值的時候,它的willSet和didSet監視器都會被調用,甚至當新的值和如今的值徹底相同也會調用。
例子中的willSet監視器將表示新值的參數自定義爲newTotalSteps,這個監視器只是簡單的將新的值輸出。
didSet監視器在totalSteps的值改變後被調用,它把新的值和舊的值進行對比,若是總的步數增長了,就輸出一個消息表示增長了多少步。didSet沒有提供自定義名稱,因此默認值oldValue表示舊值的參數名。

注意:若是在didSet監視器裏爲屬性賦值,這個值會替換監視器以前設置的值。

 

  類型屬性

  實例屬性是屬於特定類型的實例的屬性。每次建立該類型的新實例時,它都有它本身的一組屬性值,與任何其餘實例分離,也能夠爲類型自己定義屬性,無論類型有多少個實例,這些屬性都只有惟一一份。這種屬性就是類型屬性。

類型屬性用於定義特定類型全部實例共享的數據,好比全部實例都能用的一個常量(就像 C 語言中的靜態常量),或者全部實例都能訪問的一個變量(就像 C 語言中的靜態變量)。對於值類型(指結構體和枚舉)能夠定義存儲型和計算型類型屬性,對於類(class)則只能定義計算型類型屬性。
 
值類型的存儲型類型屬性能夠是變量或常量,計算型類型屬性跟實例的計算屬性同樣定義成變量屬性。
 
注意:跟實例的存儲屬性不一樣,必須給存儲型類型屬性指定默認值,由於類型自己沒法在初始化過程當中使用構造器給類型屬性賦值。

 

  類型屬性語法

使用關鍵字static來定義值類型的類型屬性,關鍵字class來爲類(class)定義類型屬性。下面的例子演示了存儲型和計算型類型屬性的語法

struct SomeStructure { 
    static var storedTypeProperty = "Some value." 
    static var computedTypeProperty: Int { 
    // 這裏返回一個 Int 值 
    } 
} 
enum SomeEnumeration { 
    static var storedTypeProperty = "Some value." 
    static var computedTypeProperty: Int { 
    // 這裏返回一個 Int 值 
    } 
} 
class SomeClass { 
    class var computedTypeProperty: Int { 
    // 這裏返回一個 Int 值 
    } 
} 

 

  獲取和設置類型屬性的值

實例的屬性同樣,類型屬性的訪問也是經過點運算符來進行,可是,類型屬性是經過類型自己來獲取和設置,而不是經過實例。好比

println(SomeClass.computedTypeProperty) 
// 輸出 "42" 
 
println(SomeStructure.storedTypeProperty) 
// 輸出 "Some value." 
SomeStructure.storedTypeProperty = "Another value." 
println(SomeStructure.storedTypeProperty) 
// 輸出 "Another value.」 

下面的例子定義了一個結構體,使用兩個存儲型類型屬性來表示多個聲道的聲音電平值,每一個聲道有一個 0 到 10 之間的整數表示聲音電平值。

 
後面的圖表展現瞭如何聯合使用兩個聲道來表示一個立體聲的聲音電平值。當聲道的電平值是 0,沒有一個燈會亮;當聲道的電平值是 10,全部燈點亮。本圖中,左聲道的電平是 9,右聲道的電平是 7。

struct AudioChannel { 
    static let thresholdLevel = 10 
    static var maxInputLevelForAllChannels = 0 
    var currentLevel: Int = 0 { 
    didSet { 
        if currentLevel > AudioChannel.thresholdLevel { 
            // 將新電平值設置爲閥值 
            currentLevel = AudioChannel.thresholdLevel 
        } 
        if currentLevel > AudioChannel.maxInputLevelForAllChannels { 
            // 存儲當前電平值做爲新的最大輸入電平 
            AudioChannel.maxInputLevelForAllChannels = currentLevel 
        } 
    } 
    } 
} 

結構AudioChannel定義了 2 個存儲型類型屬性來實現上述功能。第一個是thresholdLevel,表示聲音電平的最大上限閾值,它是一個取值爲 10 的常量,對全部實例均可見,若是聲音電平高於 10,則取最大上限值 10(見後面描述)。

 
第二個類型屬性是變量存儲型屬性maxInputLevelForAllChannels,它用來表示全部AudioChannel實例的電平值的最大值,初始值是 0。
 
AudioChannel也定義了一個名爲currentLevel的實例存儲屬性,表示當前聲道如今的電平值,取值爲 0 到 10。
 
屬性currentLevel包含didSet屬性監視器來檢查每次新設置後的屬性值,有以下兩個檢查:
 
若是currentLevel的新值大於容許的閾值thresholdLevel,屬性監視器將currentLevel的值限定爲閾值thresholdLevel。
若是修正後的currentLevel值大於任何以前任意AudioChannel實例中的值,屬性監視器將新值保存在靜態屬性maxInputLevelForAllChannels中。
 
注意:在第一個檢查過程當中,didSet屬性監視器將currentLevel設置成了不一樣的值,但這時不會再次調用屬性監視器。
 
可使用結構體AudioChannel來建立表示立體聲系統的兩個聲道leftChannel和rightChannel:
var leftChannel = AudioChannel() 
var rightChannel = AudioChannel() 

 若是將左聲道的電平設置成 7,類型屬性maxInputLevelForAllChannels也會更新成 7:

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

若是試圖將右聲道的電平設置成 11,則會將右聲道的currentLevel修正到最大值 10,同時maxInputLevelForAllChannels的值也會更新到 10:

rightChannel.currentLevel = 11  
println(rightChannel.currentLevel)  
// 輸出 "10"  
println(AudioChannel.maxInputLevelForAllChannels)  
相關文章
相關標籤/搜索