- 計(jì)算屬性可以用于類勒庄、結(jié)構(gòu)體和枚舉串前,
- 存儲(chǔ)屬性只能用于類和結(jié)構(gòu)體。
枚舉只能有計(jì)算屬性
- 屬性觀察器可以添加到自己定義的存儲(chǔ)屬性上实蔽,也可以添加到從父類繼承的屬性上荡碾。
- 屬性觀察者是對(duì)存儲(chǔ)屬性而言的,類和結(jié)構(gòu)體都可以用局装,但枚舉不行坛吁。對(duì)于類來(lái)說(shuō),屬性觀察者可以觀察基類成員铐尚。
- 支持多用屬性觀察者拨脉,相比于KVO難用的API,這個(gè)方便多了
- 屬性也可以直接作用于類型本身宣增,這種屬性稱為類型屬性玫膀。
靜態(tài)屬性或者類屬性
存儲(chǔ)屬性(Stored Properties)
- 存儲(chǔ)屬性可以是變量存儲(chǔ)屬性(用關(guān)鍵字 var 定義),也可以是常量存儲(chǔ)屬性(用關(guān)鍵字 let 定義)爹脾。
默認(rèn)都定義為let帖旨,實(shí)在有必要的才用var,好習(xí)慣一開(kāi)始就要養(yǎng)成
- 可以在定義存儲(chǔ)屬性的時(shí)候指定默認(rèn)值誉简,也可以在構(gòu)造過(guò)程中設(shè)置或修改存儲(chǔ)屬性的值碉就,甚至修改常量存儲(chǔ)屬性的值。
- ViewModel推薦給默認(rèn)值闷串,用普通變量瓮钥。就算不顯示,也先給一個(gè)“”
- Model推薦不設(shè)默認(rèn)值烹吵,用可選類型碉熄,模擬現(xiàn)實(shí)中收不到網(wǎng)絡(luò)數(shù)據(jù)的情況
- 如果創(chuàng)建了一個(gè)結(jié)構(gòu)體的實(shí)例并將其賦值給一個(gè)常量,則無(wú)法修改該實(shí)例的任何屬性肋拔,即使有屬性被聲明為變量也不行锈津。
- 這種行為是由于結(jié)構(gòu)體(struct)屬于值類型。當(dāng)值類型的實(shí)例被聲明為常量的時(shí)候凉蜂,它的所有屬性也就成了常量琼梆。
- 屬于引用類型的類(class)則不一樣。把一個(gè)引用類型的實(shí)例賦給一個(gè)常量后窿吩,仍然可以修改該實(shí)例的變量屬性茎杂。
- 延遲存儲(chǔ)屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會(huì)計(jì)算其初始值的屬性。
- 在屬性聲明前使用 lazy 來(lái)標(biāo)示一個(gè)延遲存儲(chǔ)屬性纫雁。
延遲屬性很有用煌往,當(dāng)屬性的值依賴于在實(shí)例的構(gòu)造過(guò)程結(jié)束后才會(huì)知道影響值的外部因素時(shí),或者當(dāng)獲得屬性的初始值需要復(fù)雜或大量計(jì)算時(shí)轧邪,可以只在需要的時(shí)候計(jì)算它刽脖。- 必須將延遲存儲(chǔ)屬性聲明成變量(使用 var 關(guān)鍵字)羞海,因?yàn)閷傩缘某跏贾悼赡茉趯?shí)例構(gòu)造完成之后才會(huì)得到。而常量屬性在構(gòu)造過(guò)程完成之前必須要有初始值曲管,因此無(wú)法聲明成延遲屬性却邓。
- 如果一個(gè)被標(biāo)記為 lazy 的屬性在沒(méi)有初始化時(shí)就同時(shí)被多個(gè)線程訪問(wèn),則無(wú)法保證該屬性只會(huì)被初始化一次翘地。也就是說(shuō)lazy屬性不是線程安全的申尤,在多線程條件下要注意
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)建
print(manager.importer.fileName)
// DataImporter 實(shí)例的 importer 屬性現(xiàn)在被創(chuàng)建了
// 輸出 "data.txt”
- Swift 中的屬性沒(méi)有對(duì)應(yīng)的實(shí)例變量,屬性的后端存儲(chǔ)也無(wú)法直接訪問(wèn)勺远。
Object-C中屬性其實(shí)是get橙喘、set函數(shù),_屬性名 是其背后的成員變量胶逢。一般情況厅瞎,屬性名用于外部訪問(wèn),內(nèi)部用下劃線內(nèi)部變量初坠。在Swift中和簸,就沒(méi)有了到底用哪個(gè)的糾結(jié)了。
計(jì)算屬性(Computed Properties)
- 計(jì)算屬性不直接存儲(chǔ)值碟刺,而是提供一個(gè) getter 和一個(gè)可選的 setter锁保,來(lái)間接獲取和設(shè)置其他屬性或變量的值。
- 如果計(jì)算屬性的 setter 沒(méi)有定義表示新值的參數(shù)名半沽,則可以使用默認(rèn)名稱 newValue爽柒。
- 只有 getter 沒(méi)有 setter 的計(jì)算屬性就是只讀計(jì)算屬性。只讀計(jì)算屬性總是返回一個(gè)值者填,可以通過(guò)點(diǎn)運(yùn)算符訪問(wèn)浩村,但不能設(shè)置新的值。
- 必須使用 var 關(guān)鍵字定義計(jì)算屬性占哟,包括只讀計(jì)算屬性心墅,因?yàn)樗鼈兊闹挡皇枪潭ǖ摹?/li>
- 只讀計(jì)算屬性的聲明可以去掉 get 關(guān)鍵字和花括號(hào)
支持多用只讀計(jì)算屬性,多用這種方便寫法
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)”
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)
}
}
}
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"
屬性觀察器(Property Observers)
屬性觀察器監(jiān)控和響應(yīng)屬性值的變化榨乎,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器怎燥,即使新值和當(dāng)前值相同的時(shí)候也不例外。
可以為除了延遲存儲(chǔ)屬性之外的其他存儲(chǔ)屬性添加屬性觀察器谬哀。
lazy 修飾的屬性不能添加屬性觀察器
可以通過(guò)重寫屬性的方式為繼承的屬性(包括存儲(chǔ)屬性和計(jì)算屬性)添加屬性觀察器刺覆。
不必為非重寫的計(jì)算屬性添加屬性觀察器,因?yàn)榭梢酝ㄟ^(guò)它的getter, setter 直接監(jiān)控和響應(yīng)值的變化史煎。
willSet 在新的值被設(shè)置之前調(diào)用
willSet 觀察器會(huì)將新的屬性值作為常量參數(shù)傳入谦屑,在 willSet 的實(shí)現(xiàn)代碼中可以為這個(gè)參數(shù)指定一個(gè)名稱侵贵,如果不指定則參數(shù)仍然可用,這時(shí)使用默認(rèn)名稱 newValue 表示蔼两。
- didSet 在新的值被設(shè)置之后立即調(diào)用
didSet 觀察器會(huì)將舊的屬性值作為參數(shù)傳入匈仗,可以為該參數(shù)命名或者使用默認(rèn)參數(shù)名 oldValue。如果在 didSet 方法中再次對(duì)該屬性賦值悍手,那么新值會(huì)覆蓋舊的值帘睦。
如果將屬性通過(guò) in-out 方式傳入函數(shù),willSet 和 didSet 也會(huì)調(diào)用坦康。這是因?yàn)?in-out 參數(shù)采用了拷入拷出模式:即在函數(shù)內(nèi)部使用的是參數(shù)的 copy竣付,函數(shù)結(jié)束后,又對(duì)參數(shù)重新賦值滞欠。
父類的屬性在子類的構(gòu)造器中被賦值時(shí)古胆,它在父類中的 willSet 和 didSet 觀察器會(huì)被調(diào)用,隨后才會(huì)調(diào)用子類的觀察器筛璧。在父類初始化方法調(diào)用之前逸绎,子類給屬性賦值時(shí),觀察器不會(huì)被調(diào)用夭谤。
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
全局變量和局部變量(Global and Local Variables)
- 計(jì)算屬性和屬性觀察器所描述的功能也可以用于全局變量和局部變量棺牧。
- 在全局或局部范圍都可以定義計(jì)算型變量和為存儲(chǔ)型變量定義觀察器。計(jì)算型變量跟計(jì)算屬性一樣朗儒,返回一個(gè)計(jì)算結(jié)果而不是存儲(chǔ)值颊乘,聲明格式也完全一樣。
- 全局的常量或變量都是延遲計(jì)算的采蚀,跟延遲存儲(chǔ)屬性相似疲牵,不同的地方在于,全局的常量或變量不需要標(biāo)記lazy修飾符榆鼠。
- 局部范圍的常量或變量從不延遲計(jì)算纲爸。
類型屬性(Type Properties)
- 類型屬性用于定義某個(gè)類型所有實(shí)例共享的數(shù)據(jù),比如所有實(shí)例都能用的一個(gè)常量(就像 C 語(yǔ)言中的靜態(tài)常量)妆够,或者所有實(shí)例都能訪問(wèn)的一個(gè)變量(就像 C 語(yǔ)言中的靜態(tài)變量)识啦。
- 存儲(chǔ)型類型屬性可以是變量或常量,計(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 修飾符。
- 類型屬性是作為類型定義的一部分寫在類型最外層的花括號(hào)內(nèi)乍楚,因此它的作用范圍也就在類型支持的范圍內(nèi)当编。
- 使用關(guān)鍵字 static 來(lái)定義類型屬性。
- 在為類定義計(jì)算型類型屬性時(shí)徒溪,可以改用關(guān)鍵字 class 來(lái)支持子類對(duì)父類的實(shí)現(xiàn)進(jìn)行重寫忿偷。
- 跟實(shí)例屬性一樣,類型屬性也是通過(guò)點(diǎn)運(yùn)算符來(lái)訪問(wèn)臊泌。但是鲤桥,類型屬性是通過(guò)類型本身來(lái)訪問(wèn),而不是通過(guò)實(shí)例缺虐。
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ì)算屬性,重寫
}
}
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 {
// 將當(dāng)前音量限制在閥值之內(nèi)
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
// 存儲(chǔ)當(dāng)前音量作為新的最大輸入音量
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// 輸出 "7"
print(AudioChannel.maxInputLevelForAllChannels)
// 輸出 "7"
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// 輸出 "10"
print(AudioChannel.maxInputLevelForAllChannels)
// 輸出 "10"
- 類變量用static修飾高氮,在一個(gè)struct內(nèi),用上了let或者var顷牌,這種方式可以替代define
- 孤零零的全局變量盡量不要用剪芍,放在某個(gè)struct內(nèi)進(jìn)行歸類。實(shí)在想用窟蓝,也可以包裝成一個(gè)單例再用罪裹。雖然不需要寫external,用起來(lái)方便运挫,但全局變量的副作用一點(diǎn)也沒(méi)有被限制状共。
- 計(jì)算屬性是getter,setter谁帕;屬性觀察器是willSet和didSet峡继;newValue,oldValue是隱含變量匈挖,方便使用碾牌;屬性觀察器需要給默認(rèn)值,初始化(如果是存儲(chǔ)屬性)儡循;計(jì)算屬性本質(zhì)上像函數(shù)舶吗,不需要初始化,不需要默認(rèn)值择膝。這兩者在代碼結(jié)構(gòu)上有點(diǎn)類似誓琼,需要區(qū)分清楚
- lazy 標(biāo)記的屬性要按普通的屬性一樣初始化。只不過(guò)其真正執(zhí)行初始化代碼在調(diào)用的時(shí)候。
- 屬性觀察器可以替代部分KVO的功能腹侣,但是用起來(lái)比KVO方便多了呵扛,鼓勵(lì)多用。
- 類的類屬性筐带,也統(tǒng)一用static吧今穿,跟struct一樣,好記又方便伦籍,基本就可以了蓝晒。用class,計(jì)算屬性帖鸦,類才行芝薇,還要重寫基類屬性。這么復(fù)雜作儿,有必要用嗎洛二?