前言
屬性將值跟特定的類熄浓、結(jié)構(gòu)或枚舉關(guān)聯(lián)氧秘。存儲(chǔ)屬性存儲(chǔ)常量或變量作為實(shí)例的一部分年鸳,而計(jì)算屬性計(jì)算(不是存儲(chǔ))一個(gè)值。計(jì)算屬性可以用于類丸相、結(jié)構(gòu)體和枚舉搔确,存儲(chǔ)屬性只能用于類和結(jié)構(gòu)體。
存儲(chǔ)屬性和計(jì)算屬性通常與特定類型的實(shí)例關(guān)聯(lián)灭忠。但是膳算,屬性也可以直接作用于類型本身,這種屬性稱為類型屬性弛作。
另外畦幢,還可以定義屬性觀察器來(lái)監(jiān)控屬性值的變化,以此來(lái)觸發(fā)一個(gè)自定義的操作缆蝉。屬性觀察器可以添加到自己定義的存儲(chǔ)屬性上,也可以添加到從父類繼承的屬性上瘦真。
Swift
屬性可以分為 實(shí)例屬性和類型屬性
實(shí)例屬性
實(shí)例屬性可以分為 存儲(chǔ)屬性 和 計(jì)算屬性刊头。
存儲(chǔ)屬性
一個(gè)存儲(chǔ)屬性就是存儲(chǔ)在特定類或結(jié)構(gòu)體實(shí)例里的一個(gè)常量或變量。存儲(chǔ)屬性可以是變量存儲(chǔ)屬性(用關(guān)鍵字 var
定義)诸尽,也可以是常量存儲(chǔ)屬性(用關(guān)鍵字 let
定義)原杂。
- 類似于成員變量
- 存儲(chǔ)在實(shí)例的內(nèi)存中
- 結(jié)構(gòu)體、類可以定義存儲(chǔ)屬性
- 枚舉
定義存儲(chǔ)屬性
關(guān)于存儲(chǔ)屬性您机,Swift
中明確規(guī)定穿肄,在創(chuàng)建類或者結(jié)構(gòu)體的實(shí)例時(shí),必須為所有的存儲(chǔ)屬性設(shè)置一個(gè)合適的初始值:
1)可以在初始化器里為存儲(chǔ)屬性設(shè)置設(shè)置一個(gè)初始值际看;
2)可以分配一個(gè)默認(rèn)的屬性值作為屬性定義的一部分咸产。
struct Point {
var x: Int
let y: Int
}
var p1 = Point(x: 1, y: 1)
p1.x = 2
p1.y = 2 // 報(bào)錯(cuò),y 是常量存儲(chǔ)屬性
let p2 = Point(x: 10, y: 10)
p2.x = 12 // 報(bào)錯(cuò)仲闽,結(jié)構(gòu)體(Point)是值類型脑溢。當(dāng)值類型的實(shí)例被聲明為常量的時(shí)候,其屬性也就成了常量
class Point {
var x: Int
let y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = Point(x: 1, y: 1)
p1.x = 2
p1.y = 2 // 報(bào)錯(cuò)赖欣,y 是常量存儲(chǔ)屬性
let p2 = Point(x: 10, y: 10)
p2.x = 12 // 不報(bào)錯(cuò)屑彻,類引用類型验庙,把引用類型的實(shí)例賦給一個(gè)常量后,仍然可以修改該實(shí)例的變量屬性社牲。
延遲存儲(chǔ)屬性
延遲存儲(chǔ)屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會(huì)計(jì)算其初始值的屬性粪薛。在屬性聲明前使用 lazy
來(lái)標(biāo)示一個(gè)延遲存儲(chǔ)屬性。
實(shí)例:Person
類和 House
類搏恤,Person
類中有個(gè)存儲(chǔ)屬性 house
违寿,在實(shí)例化 Person
對(duì)象的時(shí)候由于 house
使用了 lazy
,所以 house
只有在被第一使用的時(shí)候才會(huì)創(chuàng)建(調(diào)用 House
類中的相關(guān)方法)
class House {
var name: String
init(name: String) {
self.name = name
print("init \(name) House")
}
func sleep() {
print("sleep")
}
}
class Person {
lazy var house = House(name: "天橋")
init() {
print("init Person")
}
func goHome() {
house.sleep()
}
}
let p = Person()
print("House 還沒(méi)被創(chuàng)建")
p.goHome()
輸出結(jié)果:
init Person
House 還沒(méi)被創(chuàng)建
init 天橋 House
sleep
如果不使用 lazy
則:
init 天橋 House
init Person
House 還沒(méi)被創(chuàng)建
sleep
延遲屬性很有用挑社,當(dāng)屬性的值依賴于在實(shí)例的構(gòu)造過(guò)程結(jié)束后才會(huì)知道影響值的外部因素時(shí)陨界,或者當(dāng)獲得屬性的初始值需要復(fù)雜或大量計(jì)算時(shí),可以只在需要的時(shí)候計(jì)算它痛阻。
? 1.必須將延遲存儲(chǔ)屬性聲明成變量(使用 var
關(guān)鍵字)菌瘪,因?yàn)閷傩缘某跏贾悼赡茉趯?shí)例構(gòu)造完成之后才會(huì)得到。而常量屬性(let
)在構(gòu)造過(guò)程完成之前必須要有初始值阱当,因此無(wú)法聲明成延遲屬性俏扩。
? 2. 如果一個(gè)被標(biāo)記為 lazy
的屬性在沒(méi)有初始化時(shí)就同時(shí)被多個(gè)線程訪問(wèn),則無(wú)法保證該屬性只會(huì)被初始化一次弊添。
計(jì)算屬性(Computed Property)
計(jì)算屬性不直接存儲(chǔ)值录淡,不用初始化,而是提供一個(gè) getter
和一個(gè)可選的 setter
油坝,來(lái)間接獲取和設(shè)置其他屬性或變量的值嫉戚。
- 本質(zhì)就是方法(函數(shù))
- 不占用實(shí)例的內(nèi)存
- 枚舉、結(jié)構(gòu)體澈圈、類都可以定義計(jì)算屬性
struct Square {
var width: Double
var are: Double {
get {
width * width // 只有一條 return 語(yǔ)句彬檀,省略 return 關(guān)鍵字
}
set(newAre){
width = sqrt(newAre)
}
/**
set 傳入新值的默認(rèn)默認(rèn)名稱為 newValue,所以可以簡(jiǎn)化如下
set {
width = sqrt(newValue)
}
*/
}
}
var s = Square(width: 3)
print("width:\(s.width), are:\(s.are)")
計(jì)算屬性的 set
方法瞬女,傳入的新值默認(rèn)叫 newValue
窍帝,如上面實(shí)例中注釋掉的 set
方法,當(dāng)然也可以自定義新值的名稱诽偷,如上面的 newAre
坤学。
只讀計(jì)算屬性
只有 getter
沒(méi)有 setter
的計(jì)算屬性就是只讀計(jì)算屬性。只讀計(jì)算屬性總是返回一個(gè)值报慕,可以通過(guò)點(diǎn)運(yùn)算符訪問(wèn)深浮,但不能設(shè)置新的值。
struct Square {
var width = 0.0
var are:Double {
get {
width * width
}
}
}
var s = Square(width: 2)
print("are:\(s.are)")
只讀計(jì)算屬性的聲明可以去掉 get
關(guān)鍵字和花括號(hào)眠冈,所以:
struct Square {
var width = 0.0
var are: Double {
width * width
}
}
必須使用 var
關(guān)鍵字定義計(jì)算屬性略号,包括只讀計(jì)算屬性,因?yàn)樗鼈兊闹挡皇枪潭ǖ摹?code>let 關(guān)鍵字只用來(lái)聲明常量屬性,表示初始化后再也無(wú)法修改的值玄柠。
屬性觀察器
屬性觀察器監(jiān)控和響應(yīng)屬性值的變化突梦,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器,即使新值和當(dāng)前值相同的時(shí)候也不例外羽利。
可以為非
lazy
修飾的 var
的存儲(chǔ)屬性添加觀察器宫患。
class Animal {
var age: Int {
//新值存儲(chǔ)之前調(diào)用
willSet {
print("Animal willSet newValue:\(newValue)")
}
//新值存儲(chǔ)之后調(diào)用
didSet {
print("Animal didSet oldValue:\(oldValue), age:\(age)")
}
}
init(age: Int) {
self.age = age
}
}
var a = Animal(age: 1)
a.age = 5
Animal willSet newValue:5
Animal didSet oldValue:1, age:5
-
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ì)覆蓋舊的值蛋辈。 - 在初始化器中設(shè)置屬性值属拾,不會(huì)出發(fā)
willSet
和didSet
。
父類和子類針對(duì)同一個(gè)存儲(chǔ)屬性同時(shí)存在屬性觀察器
class Dog: Animal {
override var age: Int {
willSet {
print("Dog willSet newValue:\(newValue)")
}
didSet {
print("Dog didSet oldValue:\(oldValue), age:\(age)")
}
}
}
var d = Dog(age: 6)
d.age = 10
Dog willSet newValue:10
Animal willSet newValue:10
Animal didSet oldValue:6, age:10
Dog didSet oldValue:6, age:10
對(duì)于同一個(gè)屬性冷溶,子類和父類都有屬性觀察者渐白,其順序是:先子類willSet
,后父類 willSet
逞频,再父類 didSet
纯衍, 子類的 didSet
父類的屬性在子類的構(gòu)造器中被賦值時(shí),它在父類中的 willSet
和 didSet
觀察器會(huì)被調(diào)用苗胀,隨后才會(huì)調(diào)用子類的觀察器襟诸。
番外 - 各種實(shí)例屬性通過(guò) inout
方式傳入函數(shù)
存儲(chǔ)屬性通過(guò) inout
方式傳入函數(shù)
func inoutFunc(v1: inout Int) {
v1 = 666
print("inoutFunc")
}
struct Line {
var width = 1
}
var l = Line()
inoutFunc(v1: &l.width)
print("Line inout:\(l.width)") //輸出 Line inout:666
帶有屬性觀察器的存儲(chǔ)屬性通過(guò) inout
方式傳入函數(shù)
struct Line {
var width = 1 {
willSet {
print("Line willSet")
}
didSet {
print("Line didSet")
}
}
}
var l = Line()
inoutFunc(v1: &l.width)
print("Line inout:\(l.width)")
輸出信息:
inoutFunc
Line willSet
Line didSet
Line inout:666
可以看到在 inoutFunc
函數(shù)打印信息之后調(diào)用了 willSet
和 didSet
計(jì)算屬性通過(guò) inout
方式傳入函數(shù)
struct Line {
var width = 1
var widthTest: Int {
set {
width = newValue
print("Line set")
}
get {
print("Line get")
return width
}
}
}
var l = Line()
inoutFunc(v1: &l.widthTest)
print("Line inout:\(l.width)")
輸出:
Line get
inoutFunc
Line set
Line inout:666
可以看到,傳入 inoutFunc
函數(shù)時(shí)基协,通過(guò) get
取到值歌亲,完成 inoutFunc
后,又調(diào)用了 set
方法堡掏,設(shè)置值。
- 如果實(shí)參有物理內(nèi)存地址刨疼,且沒(méi)有設(shè)置屬性觀察器
直接將實(shí)參的內(nèi)存地址傳入函數(shù)(實(shí)參進(jìn)行引用傳遞) - 如果實(shí)參是計(jì)算屬性 或者 設(shè)置了屬性觀察器
采取了 拷入拷出模式(在函數(shù)內(nèi)部使用的是參數(shù)的copy
泉唁,函數(shù)結(jié)束后,又對(duì)參數(shù)重新賦值揩慕。)
1.調(diào)用該函數(shù)時(shí)亭畜,先復(fù)制實(shí)參的值,產(chǎn)生副本【get】
2.將副本的內(nèi)存地址傳入函數(shù)(副本進(jìn)行引用傳遞)迎卤,在函數(shù)內(nèi)部可以修改副本的值
3.函數(shù)返回后拴鸵,再將副本的值覆蓋實(shí)參的值【set】
總結(jié): inout
的本質(zhì)就是引用傳遞(地址傳遞)
全局變量和局部變量
計(jì)算屬性和屬性觀察器所描述的功能也可以用于全局變量和局部變量。全局變量是在函數(shù)、方法劲藐、閉包或任何類型之外定義的變量八堡。局部變量是在函數(shù)、方法或閉包內(nèi)部定義的變量聘芜。
全局或局部變量都屬于存儲(chǔ)型變量兄渺,跟存儲(chǔ)屬性類似,它為特定類型的值提供存儲(chǔ)空間汰现,并允許讀取和寫(xiě)入挂谍。
另外,在全局或局部范圍都可以定義計(jì)算型變量和為存儲(chǔ)型變量定義觀察器瞎饲。計(jì)算型變量跟計(jì)算屬性一樣口叙,返回一個(gè)計(jì)算結(jié)果而不是存儲(chǔ)值,聲明格式也完全一樣嗅战。
var number: Int {
set {
print("number set")
}
get {
print("number get")
return 1
}
}
number = 11
print(number)
打印信息:
number set
number get
1
類型屬性
為類型本身定義屬性妄田,無(wú)論創(chuàng)建了多少個(gè)該類型的實(shí)例,這些屬性都只有唯一一份仗哨,這種屬性就是類型屬性形庭。只能通過(guò)類型去訪問(wèn)。
類型屬性可以分為:存儲(chǔ)型類型屬性和計(jì)算型類型屬性厌漂。
存儲(chǔ)型類型屬性 - 可以是變量或常量萨醒,在整個(gè)程序運(yùn)行過(guò)程中,只有 1 份內(nèi)存(類似全局變量)苇倡。
計(jì)算型類型屬性 - 跟實(shí)例的計(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
修飾符好港。 - 存儲(chǔ)型類型屬性在初始化的時(shí)候,通過(guò)斷點(diǎn)調(diào)試可以看到起調(diào)用 swift_once 并最終調(diào)用 dispatch_once 對(duì)存儲(chǔ)型類型屬性進(jìn)行初始化(賦初值)
使用關(guān)鍵字 static
來(lái)定義類型屬性米罚。如果是類的計(jì)算型類型屬性钧汹,可以改用關(guān)鍵字 class
。
枚舉录择、結(jié)構(gòu)體和類均可以定義類型屬性:
enum PointEnum {
static var x: Int = 0
static var y: Int {
return 0
}
}
struct PointStuct {
static var x: Int = 1
static var y: Int {
return 1
}
}
class PointClass {
static var x: Int = 2
static var y: Int {
return 2
}
class var z: Int {
return 2
}
}