Swift進階:類、對象、屬性

Swift編譯簡介

首先需要了解的是,iOS開發(fā)的語言不管是OC還是Swift,后期都是通過LLVM進行編譯的,如下圖:

可看到:
OC通過clang編譯器將OC文件編譯成IR,然后再生成可執(zhí)行文件.o
Swift則是通過Swift編譯器編譯成IR,然后生成可執(zhí)行文件。

swift在編譯過程中使用的前段編譯器是swiftc,和我們之前在OC中使用的clang是有所區(qū)別的??梢酝ㄟ^如下命令來查看swiftc都能做什么樣的事情:

swiftc -h

如下圖

可以看出:
swift文件在被編譯成可執(zhí)行文件之前,會先被編譯成SIL (Swift intermediate language)文件。

分析SIL文件之前,先新建一個class:

class YYTeacher {
    var age : Int = 20
    var name : String = "YY"
}

var t = YYTeacher()

通過SIL文件來分析Swift對象

var t = YYTeacher()這句代碼類比OC來說,實際做了兩件事情:
alloc --> 內(nèi)存分配
init --> 初始化操作
那么對于Swift來說,做了什么事情呢?下面我們通過SIL文件來觀察一下。

  • 打開終端進入項目所在目錄,輸入命令(二選一,建議使用第二條命令):
swiftc -emit-sil main.swift >> ./main.sil && open main.sil
# 用這個命令SIL文件更清晰
swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil

如果打開sil文件失敗,如圖:

則自行到sil所在目錄手動選擇使用vscode打開,如圖:

接下來看一下sil文件里面main函數(shù):

  • %0, %1...在SIL中也叫寄存器,可以理解為日常開發(fā)中的常量,一旦賦值后就不可再修改,如果SIL中還要繼續(xù)使用,就需要不斷累加數(shù)字。這里說的寄存器是虛擬的,最終運行到機器上會使用真的寄存器。
  • alloc_global后面的參數(shù)s4main1tAA9YYTeacherCvp可以通過以下命令看出是什么:
 xcrun swift-demangle s4main1tAA9YYTeacherCvp

其實就是經(jīng)過swift混寫后的字符串,如圖

可以看出:s4main1tAA9YYTeacherCvp實際就是YYTeacher里面的實例對象t.

這里也可以通過符號斷點vscode源碼調(diào)試的方法來看一下Swift內(nèi)存分配過程中發(fā)生了什么?

vscode中搜索_swift_allocObject,可以看出:

  • 綜上可總結(jié)出Swift內(nèi)存分配過程:
    __allocating_init ----> swift_allocObject ----> _swift_allocObject_ ----> swift_showAlloc ----> malloc
  • 實例對象t本質(zhì)就是_swift_allocObject_的返回類型HeapObject
  • Swift對象的內(nèi)存結(jié)構(gòu)HeapObject有兩個屬性:
struct HeapObject {
# 指針 默認(rèn)占8字節(jié)
  HeapMetadata const *metadata;
# InlineRefCounts是 class RefCounts,所以RefCounts是一個對象,默認(rèn)占8字節(jié)
  InlineRefCounts refCounts;
}

可看出HeapObject默認(rèn)占8 + 8 = 16字節(jié)
OC中,實例對象本質(zhì)則是objc_object,里面有一個class_isa,默認(rèn)8字節(jié)。

通過SIL文件來分析Swift類結(jié)構(gòu)

通過在vscode源碼分析可得如下圖關(guān)系所示:

可得出當(dāng)前metadata的數(shù)據(jù)結(jié)構(gòu):

struct swift_class_t {
    // 如果要與OC交互(繼承NSObject),則kind則等同于void *isa;
    void kind;

    void *superClass
    void *cacheData
    void *data

    uint32_t flags
    uint32_t instanceAddressOffset
    uint32_t instanceSize
    uint16_t instanceAlignMask
    uint16_t reserved 
    uint32_t classSize
    uint32_t classAddressOffset
    void *description
    void * IVarDestroyer
// ...
};

Swift屬性

  • 存儲屬性占用內(nèi)存空間的屬性
    在上面例子class中,默認(rèn)聲明的屬性agename就是存儲屬性,通過var(變量)或者let(常量)來修飾。

SIL文件中也可以看到:

通過查看內(nèi)存地址也可以看出:

可見agename都占用了內(nèi)存空間。

  • 計算屬性:只有getset方法,不存儲值在內(nèi)存中

如下圖:area則為計算屬性,不占用內(nèi)存空間

SIL中也可以看出area不占用內(nèi)存空間

計算屬性的本質(zhì)getset方法,方法存放在metadata元數(shù)據(jù)中(OC中則存放在objc_classMethod_list里面)

  • 屬性觀察者willSetdidSet,作用是監(jiān)聽屬性的變化
class YYTeacher {
    // 屬性觀察者
    var name : String = "YY" {
        // 新值存儲之前調(diào)用
        willSet {
            print("willSet newValue = \(newValue)")
        }
        // 新值存儲之后調(diào)用
        didSet {
            print("didSet oldValue = \(oldValue)")
        }
    }
}

var t = YYTeacher()
t.name = "newYY"

通過查看SIL文件中nameset方法:

可知:

  • willSet中可訪問到newValueself
  • didSet中可訪問到oldValueself

注意:在init方法中調(diào)用屬性不會觸發(fā)屬性觀察者的,以下面特殊情況為例。

class YYTeacher {
    var age : Int = 20
    
    var name : String = "YY" {
        // 新值存儲之前調(diào)用
        willSet {
            print("willSet newValue = \(newValue)")
        }
        // 新值存儲之后調(diào)用
        didSet {
            print("didSet oldValue = \(oldValue)")
        }
    }
    
    // 初始化當(dāng)前變量
    init() {
        // 不會觸發(fā)屬性觀察者 
        self.name = "newYY"
        self.age = 18
    }
}

var t = YYTeacher()

屬性觀察者可以定義在哪些地方呢?

  • 定義的存儲屬性
  • 繼承的存儲屬性
class YYMathTeacher: YYTeacher {
    override var age: Int {
        willSet {
            print("willSet newValue = \(newValue)")
        }
        didSet {
            print("didSet oldValue = \(oldValue)")
        }
    }
}
  • 繼承的計算屬性
    YYTeacher中計算屬性age2
 var age2 : Int {
        get {
            return age
        }
        set {
            self.age = newValue
        }
    }
class YYMathTeacher: YYTeacher {
    override var age2: Int {
        willSet {
            print("willSet newValue = \(newValue)")
        }
        didSet {
            print("didSet oldValue = \(oldValue)")
        }
    }
}

注意:定義的計算屬性里面不能添加屬性觀察者,因為get和set自己都已經(jīng)實現(xiàn)了,想要通知外界完全可以在自己的get和set方法里面操作。

如果父類和子類中的同一屬性的屬性觀察者同時存在,那么調(diào)用順序是怎樣的?

注意:在子類的init方法中調(diào)用繼承的屬性會調(diào)用屬性觀察者,因為在調(diào)用之前先調(diào)用了super.init(),確保父類變量已經(jīng)初始化完成。

  • 延遲存儲屬性
class YYTeacher {
   lazy var age : Int = 12
}
  • lazy修飾的存儲屬性
  • 延遲存儲屬性必須有一個默認(rèn)的初始值,可選類型 ? 和隱式可選類型 ! 都不行
  • 延遲存儲屬性在第一次訪問的時候才會被賦值

被第一次訪問后,查看內(nèi)存:

  • 延遲存儲屬性的本質(zhì):可選類型Optional

從上圖可以看出:延遲存儲屬性本質(zhì)上是一個可選類型Optional,在沒有被訪問之前值為nil。get方法中通過switch枚舉值,跳轉(zhuǎn)分支來進行賦值操作。

  • 延遲存儲屬性對類的內(nèi)存大小的影響
    如圖
普通存儲屬性的內(nèi)存大小
延遲存儲屬性的內(nèi)存大小

通過上面了解到,延遲存儲屬性的本質(zhì)是一個可選類型,所以來研究一下可選類型的內(nèi)存大小。

通過控制臺打印得出:
MemoryLayout<Optional<Int>>.stride = 16---> 在內(nèi)存分配的過程中,為了讓它的地址是偶地址,字節(jié)對齊后,系統(tǒng)實際分配的內(nèi)存大小。(字節(jié)對齊:以空間換取時間,提高訪問效率)
MemoryLayout<Optional<Int>>.size = 9 --- > 從存儲開始到存儲結(jié)束占用的字節(jié)大小,即實際占用的內(nèi)存大小

  • 延遲存儲屬性并不能保證線程安全

通過上面SIL中的get方法可以看到:如果有兩個線程同時訪問get方法,假如CPU線程1剛執(zhí)行到bb2時就把時間片分給了線程2,線程2也剛剛執(zhí)行到bb2的時候又將時間片分給線程1,這時線程1執(zhí)行完bb2賦值第一次,然后線程2執(zhí)行完bb2賦值第二次,所以延遲存儲屬性并不能保證只被初始化一次。

  • 類型屬性
  • 使用關(guān)鍵字static修飾
  • 類型屬性必須有一個默認(rèn)初始值
class YYTeacher {
    static var age : Int = 10
}

上面例子中age是一個類型屬性,通過YYTeacher.age來訪問它。

  • 類型屬性只會被初始化一次
    通過SIL可以看出通過static修飾的屬性是一個全局屬性

通過上圖可以看出:通過static修飾的類型屬性可以保證該屬性只被初始化一次。相比lazy來說,static聲明的類型屬性是:

  • 全局
  • 賦值過程是一個線程安全的過程
    同時,可延伸出單例的正確寫法:
    OC中單例寫法
+ (instancetype)sharedInstance {
        static Thread *sharedInstance = nil;
        static dispatch_once_t onceToken;

        dispatch_once(&onceToken, ^{
              sharedInstance = [[Thread alloc] init];
        });
        return sharedInstance;
}

Swift2.0以后的單例寫法

class YYTeacher {
    // 使用static let創(chuàng)建聲明一個實例對象
    static let sharedInstance : YYTeacher = YYTeacher();
    // 給當(dāng)前init添加訪問控制權(quán)限,不能再通過var t = YYTeacher()這種方式創(chuàng)建實例對象
    private init(){}
}
// 只能通過這種方式獲取實例變量
var t = YYTeacher.sharedInstance
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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