Swift進(jìn)階01:類、對(duì)象、屬性

第一節(jié)課:類、對(duì)象、屬性

準(zhǔn)備工具:編譯過的Swift源碼、Vscode、Xcode、終端

主要內(nèi)容:

Swift編譯簡(jiǎn)介(了解)

SIL分析(掌握)

類結(jié)構(gòu)探索

Swift屬性

Swift編譯簡(jiǎn)介

創(chuàng)建一個(gè)項(xiàng)目,寫一個(gè)類,并通過默認(rèn)的初始化,創(chuàng)建一個(gè)實(shí)例對(duì)象賦值給t。

class HZMTeacher{
 var age: Int = 18
 var name: String = "HZM"
}

let t = HZMTeacher()

接下來主要看默認(rèn)的初始化器到底做了什么操作
我們使用SIL(Swift intermediate language)來查看分析。
首先了解下什么是SIL
iOS開發(fā)語(yǔ)言不管是OC還是Swift后端都是通過LLVM進(jìn)行編譯的,如下圖所示:


01.png

可以看到:
OC通過clang編譯器,編譯成IR,然后再生成可執(zhí)行文件.o(這里也就是我們的機(jī)械碼)

Swift則是通過Swift編譯器編譯成IR,然后再生成可執(zhí)行文件。

我們?cè)賮砜匆幌?,一個(gè)swift文件的編譯過程都經(jīng)歷了什么步驟:

02.png

swift在編譯過程中使用的前端編譯器是swiftc,和我們?cè)?code>OC中使用的Clang是有區(qū)別的
我們可以通過如下命令查看swiftc都能做什么樣的事情:
swiftc -h

04.png

以上只是作為了解,擴(kuò)充一下自己知識(shí)點(diǎn),了解下Swift底層的原理,其實(shí)第一次看起來一臉懵,后面再結(jié)合分析代碼多看看了解下就好

SIL分析(要求掌握)

首先我們簡(jiǎn)單寫一段代碼


SIL分析01.png

然后通過終端執(zhí)行 swiftc -dump-ast main.swift 查看抽象語(yǔ)法樹

SIL分析02.png

這里我們簡(jiǎn)單把之前項(xiàng)目通過命令生成SIL文件并打開:swiftc -emit-sil main.swift >> ./main.sil && open main.sil

SIL分析03.png

  • 在SIL文件中搜索s4main10HZMTeacherCACycfC,其內(nèi)部實(shí)現(xiàn)主要是分配內(nèi)存+初始化變量
  • allocing_ref實(shí)際創(chuàng)建一個(gè)HZMTeacher的實(shí)例對(duì)象,當(dāng)前實(shí)例對(duì)象的引用計(jì)數(shù)為1
  • 調(diào)用init方法
// HZMTeacher.__allocating_init()
sil hidden [exact_self_class] @$s4main10HZMTeacherCACycfC : $@convention(method) (@thick HZMTeacher.Type) -> @owned HZMTeacher {
// %0 "$metatype"
bb0(%0 : $@thick HZMTeacher.Type):
  // 堆上分配內(nèi)存空間
  %1 = alloc_ref $HZMTeacher                      // user: %3
  // function_ref HZMTeacher.init() 初始化當(dāng)前變量
  %2 = function_ref @$s4main10HZMTeacherCACycfc : $@convention(method) (@owned HZMTeacher) -> @owned HZMTeacher // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned HZMTeacher) -> @owned HZMTeacher // user: %4
  // 返回
  return %3 : $HZMTeacher                         // id: %4
} // end sil function '$s4main10HZMTeacherCACycfC'

SIL語(yǔ)言對(duì)于Swift源碼的分析是非常重要的,關(guān)于其更多的語(yǔ)法信息,可以在這個(gè)網(wǎng)站進(jìn)行查詢

swift_allocObject 源碼分析

swift_allocObject的源碼如下,主要有以下幾部分:

  • 通過swift_slowAlloc分配內(nèi)存,并進(jìn)行內(nèi)存字節(jié)對(duì)齊
  • 通過new + HeapObject + metadata初始化一個(gè)實(shí)例對(duì)象
  • 函數(shù)的返回值是HeapObject類型,所以當(dāng)前對(duì)象的內(nèi)存結(jié)構(gòu)就是
  • HeapObject的內(nèi)存結(jié)構(gòu)
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));//分配內(nèi)存+字節(jié)對(duì)齊

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);//初始化一個(gè)實(shí)例對(duì)象

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}
  • 進(jìn)入swift_slowAlloc函數(shù),其內(nèi)部主要是通過malloc中分配size大小的內(nèi)存空間,并返回內(nèi)存地址,主要是用于存儲(chǔ)實(shí)例變量
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);// 堆中創(chuàng)建size大小的內(nèi)存空間,用于存儲(chǔ)實(shí)例變量
#endif
  } else {
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}

這里我們簡(jiǎn)單過了一下Swift內(nèi)存分配過程中發(fā)生的事情:

  1. _allocating_init --> swift_allocObject --> _ swift_allocObject --> swift_slowAlloc --> Malloc
  2. Swift對(duì)象的內(nèi)存結(jié)構(gòu)HrepObject,有兩個(gè)屬性:一個(gè)是Metadata,一個(gè)是Refcount,默認(rèn)占用16字節(jié)大小,就是對(duì)象中沒有任何東西也是16字節(jié)。OC中實(shí)例對(duì)象的本質(zhì)是結(jié)構(gòu)體,是以objc_object為模板繼承的,其中有一個(gè)isa指針,占8字節(jié)。Swift比OC中多了一個(gè)refCounted引用計(jì)數(shù)大小,也就是多了8字節(jié)。
  3. init在這里扮演了初始化變量的職責(zé),這和我們OC中的認(rèn)知是一樣的。

類結(jié)構(gòu)探索

看了上面的內(nèi)存分配之后,我們應(yīng)該注意到了一個(gè)Metadata,它的類型是HeapMetadata,我們來看下它的具體內(nèi)存結(jié)構(gòu)是什么?

進(jìn)入HeapMetadata定義,是TargetHeapMetaData類型的別名,接收了一個(gè)參數(shù)Inprocess
using HeapMetadata = TargetHeapMetaData<Inprocess>;
進(jìn)入TargetHeapMetaData定義,其本質(zhì)是一個(gè)模板類型,其中定義了一些所需的數(shù)據(jù)結(jié)構(gòu)。這個(gè)結(jié)構(gòu)體中沒有屬性,只有初始化方法,傳入了一個(gè)MetadataKind類型的參數(shù)(該結(jié)構(gòu)體沒有,那么只有在父類中了)這里的kind就是傳入的Inprocess

template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  //初始化方法
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
#endif
};

進(jìn)入TargetMetaData定義,有一個(gè)kind屬性,kind的類型就是之前傳入的Inprocess。從這里可以得出,對(duì)于kind,其類型就是unsigned long,主要用于區(qū)分是哪種類型的元數(shù)據(jù)

struct TargetMetaData{
   using StoredPointer = typename Runtime: StoredPointer;
    ...
    
    StoredPointer kind;
}

//******** Inprocess 定義 ********
struct Inprocess{
    ...
    using StoredPointer = uintptr_t;
    ...
}

//******** uintptr_t 定義 ********
typedef unsigned long uintptr_t;

TargetHeapMetadataTargetMetaData定義中,均可以看出初始化方法中參數(shù)kind的類型是MetadataKind

進(jìn)入MetadataKind定義,里面有一個(gè)#include "MetadataKind.def",點(diǎn)擊進(jìn)入,其中記錄了所有類型的元數(shù)據(jù),所以kind種類總結(jié)如下:

name value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x203
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF

回到TargetMetaData結(jié)構(gòu)體定義中,找方法getClassObject,在該方法中去匹配kind返回值是TargetClassMetadata類型

如果是Class,則直接對(duì)this(當(dāng)前指針,即metadata)強(qiáng)轉(zhuǎn)為ClassMetadata

 
//******** 具體實(shí)現(xiàn) ********
template<> inline const ClassMetadata *
  Metadata::getClassObject() const {
    //匹配kind
    switch (getKind()) {
      //如果kind是class
    case MetadataKind::Class: {
      // Native Swift class metadata is also the class object.
      //將當(dāng)前指針強(qiáng)轉(zhuǎn)為ClassMetadata類型
      return static_cast<const ClassMetadata *>(this);
    }
    case MetadataKind::ObjCClassWrapper: {
      // Objective-C class objects are referenced by their Swift metadata wrapper.
      auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
      return wrapper->Class;
    }
    // Other kinds of types don't have class objects.
    default:
      return nullptr;
    }
  }

這一點(diǎn),我們可以通過lldb來驗(yàn)證


源碼分析.png

po metadata->getKind(),得到其kind是Class
po metadata->getClassObject()、x/8g 0x0000000110efdc70,這個(gè)地址中存儲(chǔ)的是元數(shù)據(jù)信息!

所以,TargetMetadataTargetClassMetadata 本質(zhì)上是一樣的,因?yàn)樵趦?nèi)存結(jié)構(gòu)中,可以直接進(jìn)行指針的轉(zhuǎn)換,所以可以說,我們認(rèn)為的結(jié)構(gòu)體,其實(shí)就是TargetClassMetadata

進(jìn)入TargetClassMetadata定義,繼承自TargetAnyClassMetadata,有以下這些屬性,這也是類結(jié)構(gòu)的部分

struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
    ...
    //swift特有的標(biāo)志
    ClassFlags Flags;
    //實(shí)力對(duì)象內(nèi)存大小
    uint32_t InstanceSize;
    //實(shí)例對(duì)象內(nèi)存對(duì)齊方式
    uint16_t InstanceAlignMask;
    //運(yùn)行時(shí)保留字段
    uint16_t Reserved;
    //類的內(nèi)存大小
    uint32_t ClassSize;
    //類的內(nèi)存首地址
    uint32_t ClassAddressPoint;
  ...
}

進(jìn)入TargetAnyClassMetadata定義,繼承自TargetHeapMetadata

template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
    ...
    ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;
    TargetPointer<Runtime, void> CacheData[2];
    StoredSize Data;
    ...
}

總結(jié):

當(dāng)前類返回的實(shí)際類型是 TargetClassMetadata,而TargetMetaData中只有一個(gè)屬性kind,TargetAnyClassMetaData中有3個(gè)屬性,分別是kind, superclasscacheData

當(dāng)前Class在內(nèi)存中所存放的屬性TargetClassMetadata屬性 + TargetAnyClassMetaData屬性 + TargetMetaData屬性 構(gòu)成,所以得出的metadata的數(shù)據(jù)結(jié)構(gòu)體如下所示

    void *kind;//相當(dāng)于OC中的isa,kind的實(shí)際類型是unsigned long
    void *superClass;
    void *cacheData;
    void *data;
    uint32_t flags; //4字節(jié)
    uint32_t instanceAddressOffset;//4字節(jié)
    uint32_t instanceSize;//4字節(jié)
    uint16_t instanceAlignMask;//2字節(jié)
    uint16_t reserved;//2字節(jié)
    
    uint32_t classSize;//4字節(jié)
    uint32_t classAddressOffset;//4字節(jié)
    void *description;
    ...
}

Swift屬性

  • 存儲(chǔ)屬性:
    常量: let 修飾
    變量: var 修飾
 let age: Int = 18
 var name: String = "HZM"
}
 let t = HZMTeacher()

對(duì)于上面的age、name來說,都是我們的變量存儲(chǔ)屬性,這點(diǎn)我們?cè)赟IL文件中可以看出來


屬性01.png

特征:會(huì)占用實(shí)例對(duì)象分配的內(nèi)存空間

  • 計(jì)算屬性:不占用空間,本質(zhì)是get、set方法
    驗(yàn)證方式:使用print(class_getInstanceSize(HZMTeacher.self))來打印輸出
  • 屬性觀察者:
    willSet:新值存儲(chǔ)之前調(diào)用oldValue
    didSet:新值存儲(chǔ)之后調(diào)用newValue


    屬性02.png

*注意:在init()方法中調(diào)用修改屬性不觸發(fā)屬性觀察者


屬性03.png

注:子類與父類的調(diào)用順序如上圖:子父父子(聽到一個(gè)挺恰當(dāng)?shù)谋扔鳎瑑鹤訌母赣H那繼承了遺產(chǎn),但是想變賣的時(shí)候還是需要跟父親說一聲,父親操作一番之后再告訴兒子進(jìn)行變賣)

  • 延遲存儲(chǔ)屬性:

    1. 使用lazy修飾的存儲(chǔ)屬性
    class HZMTeacher{
      lazy var age: Int = 10
    }
    
    1. 延遲屬性必須有一個(gè)默認(rèn)的初始值


      懶加載01.png
  1. 延遲存儲(chǔ)在第一次訪問的時(shí)候才被賦值


    懶加載02.png

從圖中我們可以看出,第一個(gè)斷點(diǎn)的時(shí)候第一次訪問時(shí)值為0 第二個(gè)斷點(diǎn)的時(shí)候代表訪問過后,進(jìn)行了賦值

  1. 延遲存儲(chǔ)屬性并不能保證線程安全
    我們也可以通過sil文件來查看,這里可以在生成sil文件時(shí),加上還原swift中混淆名稱的命令(即xcrun swift-demangle):swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil
    懶加載03.png

通過分析SIL文件我們可以看到,最終走分支的時(shí)候是需要判斷值的,假定有兩個(gè)線程進(jìn)入,第一個(gè)因?yàn)橹禐榭兆吡?code>bb2,然后第二個(gè)線程進(jìn)來的時(shí)候還沒有賦值,判斷還是走了bb2流程。
所以,在此時(shí),線程1會(huì)走一遍賦值,線程2也會(huì)走一遍賦值,并不能保證屬性只初始化了一次

  1. 延遲存儲(chǔ)屬性對(duì)實(shí)例對(duì)象大小的影響


    懶加載04.png

從打印類的內(nèi)存大小可以看出,使用懶加載后內(nèi)存變大(空間換時(shí)間)

  • 類型屬性:
    類型屬性屬于這個(gè)類的本身,不管有多少個(gè)實(shí)例,類型屬性只有一份,我們使用static來修飾一個(gè)類型屬性
  1. 使用static修飾,且是全局變量
  2. 類型屬性必須有一個(gè)默認(rèn)初始值
  3. 類型屬性只會(huì)被初始化一次
?著作權(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)容

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