import Foundation
/*存儲屬性************************************************************/
//一個存儲屬性就是存儲在特定類或結構體的實例裏的一個常量或變量。
//下面的例子定義了一個名爲 FixedLengthRange 的結構體,它描述了一個在創建後無法修改值域寬度的區間:
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 該區間表示整數0,1,2
rangeOfThreeItems.firstValue = 6
// 該區間表示整數6,7,8
//FixedLengthRange 的實例包含一個名爲 firstValue 的變量存儲屬性和一個名爲 length 的常量存儲屬性。在上 面的例子中, length 在創建實例的時候被初始化,因爲它是一個常量存儲屬性,所以之後無法修改它的值
//常量結構體的存儲屬性
//如果創建了一個結構體的實例並將其賦值給一個常量,則無法修改該實例的任何屬性,即使定義了變量存儲屬性:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 該區間表示整數0,1,2,3
//rangeOfFourItems.firstValue = 6
// 儘管 firstValue 是個變量屬性,這裏還是會報錯
//因爲 rangeOfFourItems 被聲明成了常量(用 let 關鍵字),即使 firstValue 是一個變量屬性,也無法再修改它 了。
//這種行爲是由於結構體(struct)屬於值類型。當值類型的實例被聲明爲常量的時候,它的所有屬性也就成了常 量。
//屬於引用類型的類(class)則不一樣。把一個引用類型的實例賦給一個常量後,仍然可以修改該實例的變量屬 性。
//延遲存儲屬性
//延遲存儲屬性是指當第一次被調用的時候纔會計算其初始值的屬性。在屬性聲明前使用 lazy 來標示一個延遲存儲 屬性。
//必須將延遲存儲屬性聲明成變量(使用 var 關鍵字),因爲屬性的初始值可能在實例構造完成之後纔會得 到。而常量屬性在構造過程完成之前必須要有初始值,因此無法聲明成延遲屬性。
class DataImporter {
/*
DataImporter 是一個將外部文件中的數據導入的類。
這個類的初始化會消耗不少時間。
*/
var fileName = "data.txt"
// 這是提供數據導入功能
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// 這是提供數據管理功能
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter實例的importer屬性還沒有被創建
//由於使用了 lazy , importer 屬性只有在第一次被訪問的時候才被創建。比如訪問它的屬性 fileName 時:
print(manager.importer.fileName)
// DataImporter 實例的 importer 屬性現在被創建了
// 輸出 "data.txt”
//注意:如果一個被標記爲 lazy 的屬性在沒有初始化時就同時被多個線程訪問,則無法保證該屬性只會被初始化一次。
/*計算屬性************************************************************/
//除存儲屬性外,類、結構體和枚舉可以定義計算屬性。計算屬性不直接存儲值,而是提供一個 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))")
// 輸出 "square.origin is now at (10.0, 10.0)”
//這個例子定義了 3 個結構體來描述幾何形狀:
//Point封裝了一個(x,y)的座標
//Size封裝了一個width和一個height
//Rect表示一個有原點和尺寸的矩形
//Rect也提供了一個名爲 center 的計算屬性。一個矩形的中心點可以從原點( origin )和尺寸( size )算 出,所以不需要將它以顯式聲明的 Point 來保存。Rect 的計算屬性 center 提供了自定義的 getter 和 setter 來 獲取和設置矩形的中心點,就像它有一個存儲屬性一樣。
//如果計算屬性的 setter 沒有定義表示新值的參數名,則可以使用默認名稱 newValue 。下面是使用了便捷 sette r 聲明的 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 的計算屬性就是隻讀計算屬性。只讀計算屬性總是返回一個值,可以通過點運算符訪 問,但不能設置新的值。
//注意:必須使用 var 關鍵字定義計算屬性,包括只讀計算屬性,因爲它們的值不是固定的。 let 關鍵字只用來聲明常 量屬性,表示初始化後再也無法修改的值。
//只讀計算屬性的聲明可以去掉 get 關鍵字和花括號:
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
var fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
fourByFiveByTwo.width = 50
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 輸出 "the volume of fourByFiveByTwo is 40.0"
/*屬性觀察器************************************************************/
//屬性觀察器監控和響應屬性值的變化,每次屬性被設置值的時候都會調用屬性觀察器,甚至新的值和現在的值相同的時候也不例外。
//可以爲屬性添加如下的一個或全部觀察器:
//? willSet 在新的值被設置之前調用
//? didSet 在新的值被設置之後立即調用
//這裏是一個 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")
}
}
}
}
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.totalSteps = 895
// About to set totalSteps to 895
/*全局變量和局部變量************************************************************/
//計算屬性和屬性觀察器所描述的模式也可以用於全局變量和局部變量。全局變量是在函數、方法、閉包或任何類型之外定義的變量。局部變量是在函數、方法或閉包內部定義的變量
//注意:全局的常量或變量都是延遲計算的,跟延遲存儲屬性相似,不同的地方在於,全局的常量或變量不需要標記 lazy 特性。局部範圍的常量或變量不會延遲計算。
/*類型屬性************************************************************/
//實例的屬性屬於一個特定類型實例,每次類型實例化後都擁有自己的一套屬性值,實例之間的屬性相互獨立。也可以爲類型本身定義屬性,不管類型有多少個實例,這些屬性都只有唯一一份。這種屬性就是類型屬性。
//在 C 或 Objective-C 中,與某個類型關聯的靜態常量和靜態變量,是作爲全局(global)靜態變量定義的。但 是在 Swift 編程語言中,類型屬性是作爲類型定義的一部分寫在類型最外層的花括號內,因此它的作用範圍也就 在類型支持的範圍內。
//使用關鍵字 static 來定義類型屬性。在爲類(class)定義計算型類型屬性時,可以使用關鍵字 class 來支持子 類對父類的實現進行重寫。下面的例子演示了存儲型和計算型類型屬性的語法:
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
}
}
//注意: 例子中的計算型類型屬性是隻讀的,但也可以定義可讀可寫的計算型類型屬性,跟實例計算屬性的語法類似。
print(SomeStructure.storedTypeProperty)
// 輸出 "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 輸出 "Another value.”
print(SomeEnumeration.computedTypeProperty)
// 輸出 "6"
print(SomeClass.computedTypeProperty)
// 輸出 "27"
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。
//屬性 currentLevel 包含 didSet 屬性觀察器來檢查每次新設置後的屬性值,它有如下兩個檢查:
//? 如果 currentLevel 的新值大於允許的閾值 thresholdLevel ,屬性觀察器將 currentLevel 的值限定爲閾值 t hresholdLevel 。
//? 如果前一個修正後的 currentLevel 值大於任何之前任意 AudioChannel 實例中的值,屬性觀察器將新值保 存在靜態類型屬性 maxInputLevelForAllChannels 中。
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print("leftChannel.currentLevel is \(leftChannel.currentLevel)")
// 輸出"7"
print("AudioChannel.maxInputLevelForAllChannels is \(AudioChannel.maxInputLevelForAllChannels)")
// 輸出"7"
//如果試圖將右聲道的電平設置成 11,則會將右聲道的 currentLevel 修正到最大值 10,同時 maxInputLevelForAllChannels 的值也會更新到 10:
rightChannel.currentLevel = 11
print("rightChannel.currentLevel is \(rightChannel.currentLevel)")
// 輸出 "10"
print("AudioChannel.maxInputLevelForAllChannels is \(AudioChannel.maxInputLevelForAllChannels)")
// 輸出 "10"