從零學(xué)習(xí)Swift 07:屬性

總結(jié)

Swift 中的屬性分為兩大類:存儲(chǔ)屬性 , 計(jì)算屬性

一: 存儲(chǔ)屬性

存儲(chǔ)屬性類似于成員變量,定義方式很簡(jiǎn)單:

//存儲(chǔ)屬性
class Person{
    var name: String = "張三"
}

存儲(chǔ)屬性存儲(chǔ)在成員變量中;結(jié)構(gòu)體Struct和類Class都可以定義存儲(chǔ)屬性,唯獨(dú)枚舉不可以.因?yàn)槊杜e變量的內(nèi)存中只用于存儲(chǔ)case 值和關(guān)聯(lián)值,沒有用來(lái)存儲(chǔ)存儲(chǔ)屬性的內(nèi)存.

另外需要注意的是,在初始化類和結(jié)構(gòu)體的實(shí)例時(shí),必須為所有的存儲(chǔ)屬性設(shè)置初始值.

二: 計(jì)算屬性

計(jì)算屬性的定義方式需要用到set , get關(guān)鍵字:

//計(jì)算屬性
var age: Int{
  set{
    ...
  }
  get{
    ...
  }
}

像上面的age就是一個(gè)計(jì)算屬性.為了更生動(dòng)的理解計(jì)算屬性,我們以游戲賬號(hào)升級(jí)為例子.假如規(guī)則是這樣:在線兩個(gè)小時(shí),游戲賬號(hào)升級(jí)一級(jí):


//計(jì)算屬性
struct Game{
    
    //游戲時(shí)長(zhǎng),單位 : 小時(shí)
    var time: Int
    
    //游戲等級(jí),在線兩個(gè)小時(shí)升一級(jí)
    var grade: Int {
        set{
            time = newValue * 2
        }
        get{
            time / 2
        }
    }
}
//初始化 游戲時(shí)長(zhǎng)為 2 個(gè)小時(shí)
var game1 = Game(time: 2)
print("在線 \(game1.time) 個(gè)小時(shí), 游戲等級(jí)為 \(game1.grade) 級(jí)")
//設(shè)置游戲等級(jí)為 30 級(jí)
game1.grade = 30
print("在線 \(game1.time) 個(gè)小時(shí), 游戲等級(jí)為 \(game1.grade) 級(jí)")

計(jì)算屬性的本質(zhì)就是方法,這一點(diǎn)我們可以通過(guò)匯編直接看出來(lái):

調(diào)用計(jì)算屬性的 set 方法

既然計(jì)算屬性的本質(zhì)是方法,就說(shuō)明計(jì)算屬性是不會(huì)占用實(shí)例變量的內(nèi)存.因?yàn)槊杜e,結(jié)構(gòu)體,類中都可以定義方法,所以同樣也可以定義計(jì)算屬性.需要注意的是,計(jì)算屬性只能用var,不能用let.因?yàn)橛?jì)算屬性是會(huì)變化的.

枚舉rawValue的本質(zhì)

我們之前在從零學(xué)習(xí)Swift 02:枚舉和可選項(xiàng) 中說(shuō)過(guò),枚舉是不會(huì)存儲(chǔ)原始值的,今天我們就來(lái)搞清楚枚舉rawValue的本質(zhì).
首先看一下系統(tǒng)默認(rèn)的rawValue:

系統(tǒng)默認(rèn)

我們可以寫個(gè)函數(shù),實(shí)現(xiàn)rawValue的功能:

自定義 rawValue 函數(shù)

但是很奇怪,為什么系統(tǒng)的rawValue沒有括號(hào)(),其實(shí)它是計(jì)算屬性

rawValue 的本質(zhì)就是 只讀的 計(jì)算屬性

現(xiàn)在我們就搞清楚了rawValue的本質(zhì)其實(shí)就是只讀的,計(jì)算屬性

另外我們也發(fā)現(xiàn),計(jì)算屬性可以只有get沒有set,那可不可以只有set,沒有get呢?不可以,編譯器會(huì)直接報(bào)錯(cuò).

有set必須要有g(shù)et
三: 延遲存儲(chǔ)屬性

使用lazy可以定義一個(gè)延遲存儲(chǔ)屬性,在第一次使用屬性的時(shí)候才會(huì)初始化.類似于 OC 中的懶加載.

如上圖只創(chuàng)建了person,還沒有使用car,car就已經(jīng)初始化了.

我們?cè)?code>car前面加上lazy關(guān)鍵字:

延遲存儲(chǔ)屬性
  • 使用lazy延遲存儲(chǔ)屬性時(shí)要注意一下幾點(diǎn):
  1. lazy屬性必須是var,不能是let.因?yàn)?code>Swift規(guī)定let必須在實(shí)例初始化方法完成之前就有值.而lazy是用到的時(shí)候才初始化,這就沖突了.
  2. lazy屬性不是線程安全的,多個(gè)線程同時(shí)訪問同一個(gè)lazy屬性,可能不止加載一次.
  3. 當(dāng)結(jié)構(gòu)體包含一個(gè)延遲存儲(chǔ)屬性時(shí),只有var修飾的實(shí)例才能訪問延遲存儲(chǔ)屬性,let修飾的實(shí)例不允許訪問延遲存儲(chǔ)屬性,什么意思呢?看下面一張圖就知道了:
    結(jié)構(gòu)體 let 實(shí)例
四: 屬性觀察器

屬性觀察器類似于 OC 中的 KVO,它的定義方式如下:

屬性觀察器

使用屬性觀察器必須滿足三個(gè)條件:

  1. 必須是非lazy修飾(因?yàn)?lazy 屬性是在第一次訪問屬性的時(shí)候才創(chuàng)建的,而添加屬性觀察器可能會(huì)打破 lazy 的機(jī)制)
  2. 必須是var變量(既然是屬性觀察器,肯定是觀察屬性值的變化,如果用 let 常量就沒有任何意義了)
  3. 必須是存儲(chǔ)屬性(因?yàn)橛?jì)算屬性內(nèi)部本來(lái)就有一個(gè)set,可以把監(jiān)聽代碼寫到set中.)

思考一下為什么計(jì)算屬性不能設(shè)置屬性觀察器?
因?yàn)橛?jì)算屬性內(nèi)部本來(lái)就有一個(gè)set ,可以把監(jiān)聽代碼寫到set中.

五: inout 參數(shù)

之前在從零學(xué)習(xí)Swift 01:了解基礎(chǔ)語(yǔ)法中用匯編分析過(guò)inout參數(shù),知道inout輸入輸出參數(shù)是引用傳遞.今天使用更復(fù)雜的類型更深入的研究inout參數(shù)的本質(zhì).

示例代碼:


//矩形
struct Rectangle{
    //存儲(chǔ)屬性  長(zhǎng)
    var length: Int
    //屬性觀察器  寬
    var width: Int{
        willSet{
            print("newValue : ",newValue)
        }
        
        didSet{
            print("newValue : \(width) , oldValue : \(oldValue)")
        }
    }
    
    //計(jì)算屬性 (計(jì)算屬性不占用實(shí)例內(nèi)存空間,本質(zhì)是方法)
    //面積
    var area: Int{
        set{
            length = newValue / width
        }
        
        get{
            return length * width
        }
    }
    
    func show(){
        print("長(zhǎng)方形的長(zhǎng) length = \(length) , 寬 width = \(width) , 面積 area = \(area)")
    }
}

var rect = Rectangle(length: 10, width: 4)
rect.show()


func test(_ num: inout Int){
    num = 20
}

test(&rect.length)
rect.show()

上面代碼結(jié)構(gòu)體中分別有存儲(chǔ)屬性,屬性觀察器,計(jì)算屬性.下面我們就分別把這三種屬性傳入inout參數(shù).

  1. inout參數(shù)之存儲(chǔ)屬性

分析匯編:


從上圖可以看到,調(diào)用test()時(shí)直接把全局變量rect的地址作為參數(shù)傳入進(jìn)去.為什么不是把length的地址傳進(jìn)去呢?因?yàn)?code>length是結(jié)構(gòu)體的第一個(gè)成員,所以結(jié)構(gòu)體的地址就是length的地址.這里傳入rect的地址和length地址是等價(jià)的.

  1. inout參數(shù)之計(jì)算屬性

分析匯編:


從匯編語(yǔ)言中可以看到,當(dāng)inout參數(shù)傳入的是計(jì)算屬性時(shí),在調(diào)用test()方法之前會(huì)先調(diào)用計(jì)算屬性的getter方法取出值,并且把值存入棧空間;然后再調(diào)用test()方法,并且把棧空間的地址作為參數(shù)傳遞進(jìn)去.所以在test()方法內(nèi)部修改的是棧空間的值;最后再調(diào)用計(jì)算屬性的setter方法,從棧空間中取出值傳入setter方法,并賦值.

圖解:


圖解

3.inout參數(shù)之屬性觀察器

分析匯編:

從上圖的匯編中可以看到,inout參數(shù)是屬性觀察器時(shí),內(nèi)部邏輯和計(jì)算屬性很相似,都是取出值放到棧空間,然后修改棧空間的值.

為什么屬性觀察器不能像存儲(chǔ)屬性那樣,直接傳入地址,直接修改呢?因?yàn)閷傩杂^察器涉及到監(jiān)聽的邏輯.我們看看第三步的setter方法的匯編:

setter 方法

可以看到setter方法內(nèi)部會(huì)調(diào)用willSet , didSet,并且在willSet調(diào)用完之后才真正賦值.

屬性觀察器之所以要這么設(shè)計(jì)就是因?yàn)橐{(diào)用willSetdidSet.達(dá)到監(jiān)聽屬性改變的效果.因?yàn)?code>inout參數(shù)就是引用傳遞.如果直接把width的地址傳給test(),test內(nèi)部就直接修改了width的值.willSetdidSet根本就不會(huì)觸發(fā).

現(xiàn)在我們總結(jié)一下inout:
  1. inout本質(zhì)就是引用傳遞
  2. 當(dāng)inout參數(shù)是計(jì)算屬性或者設(shè)置了屬性觀察器的存儲(chǔ)屬性時(shí),采取了copy in , copy out的做法:
    2.1: 調(diào)用函數(shù)時(shí)先復(fù)制參數(shù)的值,產(chǎn)生副本 ( copy in )
    2.2: 將副本的內(nèi)存地址傳入函數(shù),在函數(shù)內(nèi)修改的是副本的值
    2.3: 將副本的值取出來(lái),覆蓋實(shí)參的值( copy out )
六: 類型屬性

上面講的屬性都是實(shí)例屬性,通過(guò)實(shí)例訪問的.Swift 中還有通過(guò)類型訪問的屬性--類型屬性.
類型屬性通過(guò)static關(guān)鍵字定義;如果是類,也可以通過(guò)class關(guān)鍵字定義.


//類型屬性
struct Person{
    static var age: Int = 1
}
Person.age = 10

類型屬性的本質(zhì)就是全局變量,在整個(gè)程序運(yùn)行過(guò)程中,只有1份內(nèi)存.
我們用匯編看一下以下代碼num1 , age , num2的內(nèi)存地址:

var num1 = 10
struct Person{
    static var age: Int = 1
}
Person.age = 11
var num2 = 12


匯編如下:

連續(xù)的內(nèi)存地址

會(huì)發(fā)現(xiàn)num1 , age , num2三個(gè)變量的地址都是連續(xù)的,說(shuō)明他們都在全局區(qū).

類型屬性還有個(gè)很重要的特性:類型屬性默認(rèn)是 lazy , 在第一次使用的時(shí)候才會(huì)初始化, 并且是線程安全的,只會(huì)初始化一次.

前面我們講延遲存儲(chǔ)屬性 lazy 關(guān)鍵字時(shí)說(shuō)過(guò),lazy不是線程安全的.為什么類型屬性默認(rèn)是lazy,它為什么是線程安全的呢?

因?yàn)樗膬?nèi)部會(huì)調(diào)用swift_once dispatch_once_f

下面我們通過(guò)匯編證明一下,首先斷點(diǎn)打到類型屬性初始化的部分,看看類型屬性初始化的函數(shù)地址.

斷點(diǎn)位置

然后運(yùn)行程序,看到類型屬性初始化函數(shù)地址為:

類型屬性初始化函數(shù)地址

接著把斷點(diǎn)調(diào)整到如圖所示位置:

運(yùn)行代碼,分析匯編如下:

第一步

進(jìn)入函數(shù):

第二步

進(jìn)入swift_once :

swift_once 函數(shù)內(nèi)部

會(huì)發(fā)現(xiàn)swift_once內(nèi)部會(huì)調(diào)用dispatch_once_f.

所以現(xiàn)在就能明白為什么類型屬性是線程安全的了,因?yàn)樗某跏蓟a放到dispatch_once_f中調(diào)用的.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,430評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,653評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,136評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,372評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,888評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,738評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,179評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評(píng)論 1 283
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,610評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,916評(píng)論 2 372

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