Swift的屬性

一、存儲(chǔ)屬性

??存儲(chǔ)屬性 是一個(gè)類或者值類型(結(jié)構(gòu)體,enum等)實(shí)例一部分的常量或變量,其存儲(chǔ)屬性一般有兩種引入方式:

  1. var關(guān)鍵字定義的,叫變量存儲(chǔ)屬性
  2. let關(guān)鍵字定義的,叫常量存儲(chǔ)屬性

那么這兩種有什么區(qū)別呢?在匯編層面,他們其實(shí)沒有什么區(qū)別,但是在編譯時(shí)他們主要的區(qū)別是:

  • let修飾的常量,其值一旦設(shè)置就無(wú)法再修改,否則編譯不通過(guò)
  • var修飾的常量,其值可以隨處設(shè)置不同的值

eg:

sil分析:

class PSYModel {
??@_hasStorage var age: Int { get set }
?? @hasStorage final let name: String { get }
?? init(
age: Int, _ name: String)
?? @objc deinit
}
??
// PSYModel.age.getter ?? //age的getter方法
// PSYModel.age.setter ?? //age的setter方法
// PSYModel.name.getter ??//name的getter方法

通過(guò)上面可以看出var修飾的變量,具有 存儲(chǔ)屬性,并且具有gettersetter方法,所以其設(shè)置值之后還可被設(shè)置新值。而let修飾的變量,被添加了final標(biāo)識(shí),并且只有getter方法,沒有setter方法,所以在被設(shè)置初值之后就不能再被設(shè)置其值。但是為啥在指定初始化器中怎么能夠給它設(shè)置值呢?因?yàn)樵?code>init方法中,是直接操作內(nèi)存,存儲(chǔ)到對(duì)應(yīng)的內(nèi)存的,并不需要調(diào)用setter方法

二、計(jì)算屬性

??在swift中存儲(chǔ)屬性很常見,當(dāng)然除了存儲(chǔ)屬性,類、結(jié)構(gòu)體和枚舉也能夠定義計(jì)算屬性。計(jì)算屬性并不存儲(chǔ)值,他們提供gettersetter方法來(lái)獲取和修改值,并且不同于存儲(chǔ)屬性可以使用varlet關(guān)鍵字修飾,計(jì)算屬性必須用var修飾,并且定義變量的類型。如下:

struct square {
??var width: Double
??
??var area: Double{
???? get{
?????? return width * width
???? }
????set(newValue){
??????self.width = newValue
????}
?? }
}
var s = square(width: 10.0)
s.area = 20

sil文件

sil文件area具有settergetter方法,但是并沒有 hasStoreage標(biāo)識(shí),也就是沒有存儲(chǔ)屬性。直接打印其結(jié)構(gòu)體的大小也可以發(fā)現(xiàn)area并不占用內(nèi)存。

如果private (set)修飾一個(gè)變量,說(shuō)明其實(shí)具有存儲(chǔ)屬性的私有變量,只能在類或結(jié)構(gòu)體代碼塊內(nèi)可以訪問(wèn),并且有settergetter方法,只是將setter方法設(shè)置為私有,外部只能調(diào)用getter方法。

三、屬性觀察者

??屬性觀察者會(huì)觀察用來(lái)觀察屬性值的變化,當(dāng)屬性值即將改變時(shí)調(diào)用willSet,當(dāng)屬性值改變之后調(diào)用didSet。其方法類似于gettersetter方法,在初始化器內(nèi)修改其值并不會(huì)觸發(fā)調(diào)用willSetdidSet方法,因?yàn)槌跏蓟魇侵苯觾?nèi)存的操作。(計(jì)算屬性不用添加觀察者

初始化器內(nèi)不會(huì)觸發(fā) 非初始化器賦值觸發(fā)

@_hasStorage var subName: String { get set } 也具有存儲(chǔ)屬性,其賦值的時(shí)候是由setter方法觸發(fā)的,sil文件如下:

subName的setter方法的sil文件

如果類存在繼承,觀察者屬性調(diào)用又是啥樣呢?

class PSYModel{
    var age: Int
    let name: String

    init(_ age: Int, _ name: String, _ subName: String) {
        self.age = age
        self.name = name
        self.subName = subName
    }
    var subName: String {
        willSet{
            print("PSY subName value willSet")
        }
        didSet{
            print("PSY subName value didSet")
        }
    }
}

class LQRModel: PSYModel {
    var sex: Int
    
    override var subName: String{
        willSet{
            print("LQR subName value willSet")
        }
        didSet{
            print("LQR subName value willSet")
        }
    }
    init(_ sex: Int) {
        self.sex = sex
        super.init(18, "P", "SY")
    }
}

var psy = LQRModel.init(1)
psy.subName = "spy"
print("end")

輸出結(jié)果:****************************
LQR subName value willSet
PSY subName value willSet
PSY subName value didSet
LQR subName value willSet
end
Program ended with exit code: 0

image.png

四、延遲存儲(chǔ)屬性

??延遲存儲(chǔ)屬性,其初始值是在第一次使用的時(shí)候才計(jì)算,使用關(guān)鍵字lazy來(lái)標(biāo)識(shí)一個(gè)延遲存儲(chǔ)屬性。延遲存儲(chǔ)屬性只能用let修飾,不能用let,且必須有值。

class PSYModel{
    lazy var age: Int = 1
    let name: String

    init(_ name: String) {
        self.name = name
    }
}
var psy = PSYModel.init("PSY")
print("psy.age = \(psy.age)")
print("end")

lldb打印內(nèi)存看一下其實(shí)例對(duì)象在初始化的時(shí)候并沒有值,在使用的時(shí)候才有值:

lldb內(nèi)存

那么它具體是怎么做到,使用的時(shí)候才有值呢?它實(shí)際是被編譯成了一個(gè)_age: Int?可選類型的存儲(chǔ)屬性,并且生成了一個(gè)_age初始化表達(dá)式,返回了一個(gè)枚舉。
sil文件

初始化表達(dá)式返回了一個(gè)枚舉
age的getter方法
bb9是初始化值為6

可知,延遲存儲(chǔ)屬性延遲了內(nèi)存的分配,相當(dāng)于懶加載一樣的效果,但是其并不是線程安全的,因?yàn)槠洳⒉荒鼙WC屬性只被訪問(wèn)一次。

五、類型屬性

??類型屬性其實(shí)是一個(gè)全局變量,只會(huì)被初始化一次,使用static修飾,eg :

class PSYModel{
    static var age: Int = 6
}
PSYModel.age = 18

將上面的代碼生成.sil代碼看一下,可以發(fā)現(xiàn)其仍具有存儲(chǔ)屬性,比一般存儲(chǔ)屬性多了個(gè)static修飾符,并且生成了一個(gè)全局變量age:

sil代碼

main函數(shù)主要意思:

  1. 引用函數(shù)PSYModel.age.unsafeMutableAddressor
  2. 調(diào)用函數(shù)PSYModel.age.unsafeMutableAddressor并轉(zhuǎn)成RawPointer原生指針
  3. 將原生指針指向Int數(shù)據(jù)類型
  4. 創(chuàng)建數(shù)值為18的數(shù)據(jù)Int64數(shù)據(jù)
  5. 將數(shù)據(jù)存入內(nèi)存
main函數(shù)

PSYModel.age.unsafeMutableAddressor到底做了啥??jī)?nèi)存是如何分配的?繼續(xù)解讀sil文件PSYModel.age.unsafeMutableAddressor,可以發(fā)現(xiàn)其主要做了:

  1. 申請(qǐng)了一個(gè)全局的token0@globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0
  2. 拿到globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0申請(qǐng)內(nèi)存函數(shù)指針
  3. 調(diào)用了一個(gè)“once”函數(shù),參數(shù)是上面得到的token0globalinit函數(shù)指針
PSYModel.age.unsafeMutableAddressor

@globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0真正初始化的函數(shù),這個(gè)就很簡(jiǎn)單了,創(chuàng)建全局屬性age,拿到內(nèi)存地址,創(chuàng)建初始值6,將6存儲(chǔ)到內(nèi)存當(dāng)中并返回。

@globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0

但是那個(gè)“once”是有什么用嗎?為什么還有一個(gè)token0?為了得到更接近底層匯編的中間代碼,在終端使用命令生成ir文件: swiftc main.swift -emit-ir -o main1.c這里為了方便生成的文件查看我制定后綴為.c文件

找到相同的函數(shù)@s4main8PSYModelC3ageSivau--> PSYModel.age.unsafeMutableAddressor,可以發(fā)現(xiàn)“once”函數(shù)的調(diào)用譯成了根據(jù)標(biāo)記,如果沒有內(nèi)存分配(once_ont_done)就調(diào)用源碼swift_once方法,如果已經(jīng)分配了(once_done)就直接讀取內(nèi)存(load),保證了其只會(huì)分配一次內(nèi)存,只初始化一次。

image.png

結(jié)合Swift源碼 看一下swift_once做了什么?全局搜索swift_once可以找到其,并根據(jù)注釋可以知道其調(diào)用了GCD(dispatch_once_f)保證函數(shù)只被執(zhí)行一次。

/// 使用給定的上下文參數(shù)運(yùn)行給定函數(shù)一次。
///predicate參數(shù)必須指向一個(gè)全局變量或靜態(tài)變量,其靜態(tài)范圍類型為swift_once_t。
void swift::swift_once(swift_once_t *predicate, void (*fn)(void *),
                       void *context) {
#ifdef SWIFT_STDLIB_SINGLE_THREADED_RUNTIME
  if (! *predicate) {
    *predicate = true;
    fn(context);
  }
#elif defined(__APPLE__)
  dispatch_once_f(predicate, context, fn);
#elif defined(__CYGWIN__)
  _swift_once_f(predicate, context, fn);
#else
  std::call_once(*predicate, [fn, context]() { fn(context); });
#endif
}
拓展(單例):

?// 單例
class PSYModel{
??static let shareInstance = PSYModel()
??private init() {}
}?
如果不希望單例類被繼承,class前面加上final

六、屬性在MachO文件中的位置

??首先我們要有一個(gè)共識(shí),根據(jù)前兩篇文章【Swift語(yǔ)言的類與結(jié)構(gòu)體--2Swift語(yǔ)言的類與結(jié)構(gòu)體--1】我們得到了Metadata的數(shù)據(jù)結(jié)構(gòu)信息:

struct Metadata{
??var kind: Int
??var superClass: Any.Type
??var cacheData: (Int, Int)
??var data: Int
??var classFlags: Int32
??var instanceAddressPoint: UInt32
??var instanceSize: UInt32
??var instanceAlignmentMask: UInt16
??var reserved: UInt16
??var classSize: UInt32
??var classAddressPoint: UInt32
??var typeDescriptor: UnsafeMutableRawPointer
??var iVarDestroyer: UnsafeRawPointer
}

以及類信息typeDescriptor的結(jié)構(gòu)信息:

struct TargetClassDescriptor{
?? var flags: UInt32
??var parent: UInt32
??var name: Int32
??var accessFunctionPointer: Int32
??var fieldDescriptor: Int32
??var superClassType: Int32
??var metadataNegativeSizeInWords: UInt32
??var metadataPositiveSizeInWords: UInt32
??var numImmediateMembers: UInt32
??var numFields: UInt32
??var fieldOffsetVectorOffset: UInt32
??var Offset: UInt32
??var size: UInt32
??//V-Table
}

以及在typeDescriptor的結(jié)構(gòu)信息中,fieldDescriptor記錄了屬性的信息,其結(jié)構(gòu)體如下:

struct FieldDescriptor{
?? var MangledTypeName: UInt32
??var Superclass: UInt32
??var Kind: Int16
??var FieldRecordSize: Int16
??var NumFields: Int32 // 屬性個(gè)數(shù)
??var FieldRecords // [FieldRecords]
}

FieldRecords中記錄每個(gè)屬性的信息,其結(jié)構(gòu)體如下:

struct FieldRecord{
??Flags :???????? uint32
??MangledTypeName:?? int32
??FieldName:?????? int32
}

新建代碼,編譯成.app文件,使用MachO View打開,手動(dòng)計(jì)算驗(yàn)證是否能找到屬性名:

class PSYModel{
    var age: Int = 18
    let name: String = "psy"
}

首先找到Section__TEXT,__swift5_types,將pFile + Data - baseAddress(基地址)0xFFFFFF5C + 0x1EE0 - 0x100000000 = 0x1E3C)得到typeDescriptor的偏移

得到typeDescriptor

點(diǎn)擊Section64__TEXT,__const,找到0x1E3CpFile,向后偏移4個(gè)4字節(jié),得到fieldDescriptor,此時(shí)也是得到fieldDescriptoroffset信息,需要pFile + Data0x1E4C+ 0x6C = 0x1EB8)才能得到真正的FieldDescriptor

得到FieldDescriptor

點(diǎn)擊Section64__TEXT,__switf5_fieldmd,找到0x1EB8,向后偏移3個(gè)4字節(jié),得到連續(xù)的內(nèi)存空間即是FiledRecords,再根據(jù)FiledRecords結(jié)構(gòu),第1個(gè)4字節(jié)是Flags,第2個(gè)4字節(jié)是MangledTypeName,接下來(lái)的4字節(jié)是FiledName的offset(0xffffffdf

得到FieldRecords

pFile + FiledName的offset0x1ED0 + 0xffffffdf = 0x100001EAF),然后在Section64__TEXT,__switf5_reflstr就可找到屬性名字如下:同理,0x1EDC + 0xffffffd7 = 0x100001EB3得到另一個(gè)屬性名name

得到FiledName

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一.存儲(chǔ)屬性 存儲(chǔ)屬性是一個(gè)作為特定類和結(jié)構(gòu)體實(shí)例一部分的常量或變量.存儲(chǔ)屬性要么是變量存儲(chǔ)屬性(由var關(guān)鍵字引...
    劉國(guó)強(qiáng)閱讀 639評(píng)論 0 0
  • Swift 屬性 在Swift中屬性主要分為存儲(chǔ)屬性、計(jì)算屬性、延遲存儲(chǔ)屬性、類型屬性這四種,并且Swift還提供...
    just東東閱讀 732評(píng)論 0 2
  • 類的屬性介紹Swift中屬性有多種存儲(chǔ)屬性:存儲(chǔ)實(shí)例的常量和變量計(jì)算屬性:通過(guò)某種方式計(jì)算出來(lái)的屬性類屬性:與整個(gè)...
    我為自己dai鹽閱讀 778評(píng)論 0 0
  • 存儲(chǔ)屬性 - Stored Properties 相當(dāng)于 OC 的下劃線成員變量 適用于:結(jié)構(gòu)體 、 類 類型:常...
    Sunday_David閱讀 392評(píng)論 0 0
  • 屬性分類在Swift中, 嚴(yán)格意義上來(lái)講屬性可以分為兩大類: 實(shí)例屬性和類型屬性 實(shí)例屬性(Instance Pr...
    1980_4b74閱讀 485評(píng)論 0 0

友情鏈接更多精彩內(nèi)容