屬性將值跟特定的類、結構或枚舉關聯。
-
存儲屬性: 存儲常量或變量作為實例的一部分。
- 計算屬性可以用于 類、結構體 和 枚舉
-
計算屬性: 計算(不是存儲)一個值。
- 存儲屬性只能用于 ** 類** 和 結構體。*
屬性分為 實例屬性 和 類型屬性
1. 存儲屬性
存儲屬性:就是存儲在特定類或結構體實例里的一個 常量 或 變量。
- 變量存儲屬性(用關鍵字 var 定義)
- 常量存儲屬性(用關鍵字 let 定義)
** 存儲屬性的默認值 **
- 存儲屬性可以在定義的時候賦值默認值。
- 可以在構造過程中設置和修改存儲屬性的值。(常量的值只可以在構造函數中進行修改)
常量結構體的存儲屬性
創建了一個結構體的實例并將其賦值給一個常量,則無法修改該實例的任何屬性,即使有屬性被聲明為變量也不行。
(常量結構體的變量屬性是不能被修改的,這個是由于結構體的值類型決定)
** 延遲存儲屬性**
延遲存儲屬性是指 當第一次被調用的時候 才會計算其 初始值 的屬性。在屬性聲明前使用 lazy 來標示一個延遲存儲屬性。
- 延遲存儲屬性必須用 var 聲明
- 被標記為 lazy 的屬性在沒有初始化時就同時被多個線程訪問,則無法保證該屬性只會被初始化一次。(加鎖)
實例屬性使用場景
- 屬性值對實例的構造過程不依賴
- 屬性的值需要經過大量計算才可以得到(耗時)
(加載本地文件數據就可以使用延遲屬性)
2. 計算屬性
- ** 計算屬性不直接存儲值,**而是提供一個 getter 和一個可選的 setter,來間接獲取和設置其他屬性或變量的值。
- 計算屬性只能用 var 來聲明。(由于計算屬性值的不確定性決定)
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()
// 計算屬性, 中心點是依賴 origin 和 size 計算得出具體的值。本身不能保存值。
var center: Point {
// getter
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
// setter (可選的)
/**
set 方法默認有一個參數 newValue 用來表示我們傳入的值。
我可以對set 的參數名稱進行進行修改,增強代碼的可讀性。
這個是 編輯 set 聲明的使用,直接使用 newValue 并且 set 的參數可以省略。
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
*/
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
只讀計算屬性:
只有 getter 沒有 setter 的計算屬性就是只讀計算屬性。
(只讀計算屬性總是返回一個固定的值,不能賦新值)
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
// 只讀計算屬性,可以省略 get 個花括號
var volume: Double {
return width * height * depth
}
}
3. 屬性觀察器(存儲屬性和計算屬性都可以添加屬性觀察器)
屬性觀察器監控和響應屬性值的變化,每次屬性被設置值的時候都會調用屬性觀察器,即使新值和當前值相同。
- 延遲存儲屬性 不能有 屬性觀察器。(重點)
- 可以通過 重寫 屬性的方式為 繼承的屬性(包括存儲屬性和計算屬性)添加屬性觀察器。
- 可以給所有的存儲屬性添加屬性觀察器。
- 不必為 非重寫 (非繼承) 的 ** 計算屬性 ** 添加屬性觀察器,因為可以通過它的 setter 直接監控和響應值的變化。
理論上屬性觀察器是可以給所有的屬性添加。(不用區分是否是存儲是否是計算。主要是要區分一下使用的場景)
可以為屬性添加如下一個或多個屬性觀察器:
- willSet: 在新的值被設置之前調用
- willSet 觀察器會將新的屬性值作為 常量參數 傳入。
在 willSet 的實現代碼中可以為這個參數指定一個名稱,不指定可以使用默認參數 newValue。
- willSet 觀察器會將新的屬性值作為 常量參數 傳入。
- didSet: 在新的值被設置之后調用
- didSet 觀察器會將舊值作為參數傳入。
在 didSet 的實現代碼中可以為這個參數指定一個名稱, 不指定可以使用默認的參數 oldValue
- didSet 觀察器會將舊值作為參數傳入。
class StepCounter {
// 這是一個存儲屬性
var totalSteps: Int = 0 {
// willSet 屬性觀察器 , 指定的傳入參數名稱為 newTotalSteps。
/*
// 這個為使用默認參數
willSet {
print("About to set totalSteps to \(newValue)")
}
*/
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
/*
// 自己定義一個參數使用,主要是為了代碼的語義化。
didSet ( tempOldValue ) {
// totalSteps 就是屬性本身。
if totalSteps > tempOldValue {
print("Added \(totalSteps - tempOldValue) steps")
}
}
*/
// 這個為使用默認參數
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
存儲屬性的使用注意:
- 父類的屬性在子類中賦值時,父類的 屬性觀察器會先被調用,后才會調用子類的屬性觀察器。
- 在父類初始化方法調用之前,子類給屬性賦值時,觀察器不會被調用。
- 屬性是通過 in-out 方式傳入, 屬性觀察器也會被調用。
* in-out 是用的是值的拷貝使用, 使用的是值的拷貝,值使用完畢后再將值拷貝出來。
4. 全局變量 和 局部變量
全局變量:是在函數、方法、閉包或任何類型之外定義的變量。
局部變量:是在函數、方法或閉包內部定義的變量。
全局或局部變量都屬于存儲型變量,跟存儲屬性類似,它為特定類型的值提供存儲空間,并允許讀取和寫入。
在全局或局部范圍都可以定義計算型變量和為存儲型變量定義觀察器。計算型變量跟計算屬性一樣,返回一個計算結果而不是存儲值,聲明格式也完全一樣。
注意:
全局的常量或變量都是延遲計算的,跟延遲存儲屬性相似,不同的地方在于,全局的常量或變量不需要標記lazy修飾符。
局部范圍的常量或變量從不延遲計算。
5. 類型屬性
實例屬性: 特定類型的實例的屬性叫實例屬性。實例之間的屬性是相互獨立的。
類型屬性:特定類型的屬性叫實例屬性。類型之間的屬性是共享的。只有唯一一份。
(類型屬性: 特么第一眼我也沒看明白。其實就是特么寫在類里面的靜態變量和靜態常量)
使用 static 來定義類型屬性(靜態變量和靜態常量)。
定義計算型類型屬性,可以改用 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 表示不允許子類重寫
static var computedTypeProperty: Int {
return 27
}
// 使用 class 表示允許子類重寫。
class var overrideableComputedTypeProperty: Int {
return 107
}
}