Swift 屬性

前言

屬性將值跟特定的類熄浓、結(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ǔ)屬性
  • 枚舉\color{red}{不可以}定義存儲(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ì)算它痛阻。

\color{red}{注意:}
? 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
    }
}

\color{red}{注意:}
必須使用 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í)候也不例外羽利。
\color{red}{注意:}可以為非 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ā) willSetdidSet

父類和子類針對(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í),它在父類中的 willSetdidSet 觀察器會(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)用了 willSetdidSet

計(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ì)算型屬性一樣只能定義成變量屬性富纸。

\color{red}{注意:}

  • 跟實(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
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拔莱,一起剝皮案震驚了整個(gè)濱河市碗降,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塘秦,老刑警劉巖讼渊,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嗤形,居然都是意外死亡精偿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)赋兵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)笔咽,“玉大人,你說(shuō)我怎么就攤上這事霹期∫蹲椋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵历造,是天一觀的道長(zhǎng)甩十。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吭产,這世上最難降的妖魔是什么侣监? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮臣淤,結(jié)果婚禮上橄霉,老公的妹妹穿的比我還像新娘。我一直安慰自己邑蒋,他們只是感情好姓蜂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著医吊,像睡著了一般钱慢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卿堂,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天束莫,我揣著相機(jī)與錄音,去河邊找鬼草描。 笑死览绿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的陶珠。 我是一名探鬼主播挟裂,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼享钞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼揍诽!你這毒婦竟也來(lái)了诀蓉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤暑脆,失蹤者是張志新(化名)和其女友劉穎渠啤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體添吗,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沥曹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碟联。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妓美。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鲤孵,靈堂內(nèi)的尸體忽然破棺而出壶栋,到底是詐尸還是另有隱情,我是刑警寧澤普监,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布贵试,位于F島的核電站,受9級(jí)特大地震影響凯正,放射性物質(zhì)發(fā)生泄漏毙玻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一廊散、第九天 我趴在偏房一處隱蔽的房頂上張望桑滩。 院中可真熱鬧,春花似錦奸汇、人聲如沸施符。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)戳吝。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背速梗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工施绎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人经瓷。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親隘马。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 存儲(chǔ)屬性 存儲(chǔ)屬性:用于存儲(chǔ)一個(gè)常量或變量 結(jié)構(gòu)體實(shí)例賦值給常量,該實(shí)例屬性不能被修改(因?yàn)榻Y(jié)構(gòu)體屬于值類型邀泉,當(dāng)值...
    皆為序幕_閱讀 1,405評(píng)論 0 0
  • 中文文檔 一嬉挡、存儲(chǔ)屬性 一個(gè)存儲(chǔ)屬性就是存儲(chǔ)在特定類或結(jié)構(gòu)體實(shí)例里的一個(gè)常量或變量。 存儲(chǔ)屬性可以是變量存儲(chǔ)屬性(...
    伯wen閱讀 199評(píng)論 0 0
  • 屬性將值跟特定的類汇恤、結(jié)構(gòu)或枚舉關(guān)聯(lián)庞钢。存儲(chǔ)屬性存儲(chǔ)常量或變量作為實(shí)例的一部分,而計(jì)算屬性計(jì)算(不是存儲(chǔ))一個(gè)值因谎。計(jì)算...
    窮人家的孩紙閱讀 414評(píng)論 0 0
  • 屬性將值跟特定的類焊夸、結(jié)構(gòu)或枚舉關(guān)聯(lián)。存儲(chǔ)屬性存儲(chǔ)常量或變量作為實(shí)例的一部分蓝角,而計(jì)算屬性計(jì)算(不是存儲(chǔ))一個(gè)值阱穗。計(jì)算...
    趙哥窟閱讀 1,221評(píng)論 0 1
  • 1. 存儲(chǔ)屬性 一個(gè)存儲(chǔ)屬性就是存儲(chǔ)在特定類或結(jié)構(gòu)體實(shí)例里的一個(gè)常量或變量。 可以是變量存儲(chǔ)屬性(用關(guān)鍵字 var...
    DevXue閱讀 210評(píng)論 0 0