swift 進(jìn)階: 類的結(jié)構(gòu)

swift 進(jìn)階之路:學(xué)習(xí)大綱

前言

  • 類似于OC的源碼分析铸题,對于swift的研究迄汛,我們也從類的創(chuàng)建為出發(fā)點進(jìn)行分析蓖墅,然后分析類的底層結(jié)構(gòu)桐磁,從而了解類的本質(zhì)等等士复。

一炭懊、類的初探

我們先看看類的創(chuàng)建流程和類的大概結(jié)構(gòu)

1.0 對象的創(chuàng)建流程

對象初始化

  • OC: [[HJPerosn alloc] init]尉姨,一般alloc申請內(nèi)存空間并創(chuàng)建對象庵朝,init對象進(jìn)行統(tǒng)一初始化處理。
  • Swift: HJPerosn()又厉,直接()就完成了對象的創(chuàng)建九府。

開啟匯編調(diào)試:

例子一:在swift工程中,創(chuàng)建一個類繼承NSObject
  • 通過斷點調(diào)試我們會發(fā)現(xiàn):這個類的創(chuàng)建流程開始走 OC的流程:__allocting_init ->objc_allocWithZone...... 原因就是HJPerson 繼承了NSObject覆致,原因是跟swift的派發(fā)機制有關(guān)侄旬,后面再深入的研究。

例子二:如下不繼承NSObject
  • 通過斷點調(diào)試我們會發(fā)現(xiàn):這個類的創(chuàng)建流程開始走 OC的流程:__allocting_init ->swift_allocObject煌妈;
  • 繼續(xù)添加符號斷點:


最后:swift對象創(chuàng)建流程:
__allocating_init -> swift_allocObject -> swift_allocObject -> swift_slowAlloc ->...

在VSCode中的源碼里儡羔,根據(jù)此流程逐漸對其源碼進(jìn)行分析如下:

1.1 swift_allocObject 源碼分析
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));//分配內(nèi)存+字節(jié)對齊

 //注意:這依賴于c++ 17保證的無空指針語義
//檢查我們在Windows上觀察到的新分配器的位置,
// Linux和macOS璧诵。
  new (object) HeapObject(metadata);//初始化一個實例對象

  //如果啟用了泄漏跟蹤笔链,請開始跟蹤此對象。
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}
  • swift_allocObject的源碼如下腮猖,主要有以下幾部分

    • 通過swift_slowAlloc分配內(nèi)存鉴扫,并進(jìn)行內(nèi)存字節(jié)對齊
    • 通過new + HeapObject + metadata初始化一個實例對象
    • 函數(shù)的返回值是HeapObject類型,所以當(dāng)前對象的內(nèi)存結(jié)構(gòu)就是HeapObject的內(nèi)存結(jié)構(gòu)
1.2 swift_allocObject 源碼分析
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  //這個檢查也強制“默認(rèn)”對齊使用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)存空間坪创,用于存儲實例變量
#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;
}
  • 進(jìn)入swift_slowAlloc函數(shù),其內(nèi)部主要是通過malloc_zone_malloc在堆中分配size大小的內(nèi)存空間姐赡,并返回內(nèi)存地址莱预,主要是用于存儲實例變量
1.3 查看HeapObject 并 計算類的大小
// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts ///引用計數(shù)

/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;/// 元數(shù)據(jù)

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS; ///引用計數(shù)

  • metadata:是 HeapMetedata類型
  • refCounts : 引用計數(shù)

【總結(jié)】

  • Swift中實例對象,默認(rèn)的比OC中多了一個refCounted引用計數(shù)大小项滑,默認(rèn)屬性占16字節(jié) : metadata(struct)8字節(jié)和refCounts(class)8字節(jié)
  • OC中實例對象的本質(zhì)是結(jié)構(gòu)體依沮,是以objc_object為模板繼承的,其中有一個isa指針枪狂,占8字節(jié)
1.4【驗證+拓展】
//驗證
class HJPerson {
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        print(class_getInstanceSize(HJPerson.self))
    }
}
打印 : 16

//拓展
class HJPerson {
    var age : Int = 20
    var name : String = "HJ"
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        print(class_getInstanceSize(HJPerson.self))
    }
}
打印 : 40

驗證的確一個空類的大小為16危喉;那么為啥第二個是40呢?
我們通過打印Int 和 String 內(nèi)存大小來驗證

//********* Int底層定義 *********
@frozen public struct Int : FixedWidthInteger, SignedInteger {...}

//String底層定義
@frozen public struct String {...}

//驗證 
print(MemoryLayout<Int>.stride)
print(MemoryLayout<String>.stride)

打印
8
16
  • 從打印的結(jié)果中可以看出州疾,Int類型占8字節(jié)辜限,String類型占16字節(jié),這點與OC中是有所區(qū)別的 严蓖,以后再進(jìn)行詳細(xì)講解吧薄嫡。

  • 所以這也解釋了為什么HJPerson的內(nèi)存大小等于40氧急,即40 = metadata(8字節(jié)) +refCount(8字節(jié))+ Int(8字節(jié))+ String(16字節(jié))

二、Swift中類的結(jié)構(gòu)探索

  • 在Swift中吩坝,類的結(jié)構(gòu)在底層是HeapObject,其中有metadata +refCounts
2.1 metadata的底層探索
  • 進(jìn)入HeapMetadata定義哑蔫,是TargetHeapMetaData類型的別名钾恢,接收了一個參數(shù)Inprocess
using HeapMetadata = TargetHeapMetaData<Inprocess>;
  • 進(jìn)入TargetHeapMetaData定義,其本質(zhì)是一個模板類型鸳址,其中定義了一些所需的數(shù)據(jù)結(jié)構(gòu)瘩蚪。這個結(jié)構(gòu)體中沒有屬性,只有初始化方法稿黍,傳入了一個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) {}

};
  • 進(jìn)入TargetMetaData定義,有一個kind屬性巡球,kind的類型就是之前傳入的Inprocess言沐。從這里可以得出,對于kind酣栈,其類型就是unsigned long险胰,主要用于區(qū)分是哪種類型的元數(shù)據(jù)
// TargetMetaData 定義 
struct TargetMetaData{
   using StoredPointer = typename Runtime: StoredPointer;
    ...
    
    StoredPointer kind;
}

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

//******** uintptr_t 定義 ********
typedef unsigned long uintptr_t;
  • 可以看出初始化方法中參數(shù)kind的類型是MetadataKind,
2.2 getClassObject
  • 回到TargetMetaData結(jié)構(gòu)體定義中,找方法getClassObject
 const TargetClassMetadata<Runtime> *getClassObject() const;
//******** 具體實現(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)前指針強轉(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;
   }
 }
  • 在該方法中去匹配kind返回值是TargetClassMetadata類型

通過lldb來驗證

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

所以TargetMetadataTargetClassMetadata本質(zhì)上是一樣的窖维,因為在內(nèi)存結(jié)構(gòu)中榆综,可以直接進(jìn)行指針的轉(zhuǎn)換,所以可以說铸史,我們認(rèn)為的結(jié)構(gòu)體鼻疮,其實就是TargetClassMetadata

2.3 TargetClassMetadata

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

template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
    ...
    //swift特有的標(biāo)志
    ClassFlags Flags;
    //實力對象內(nèi)存大小
    uint32_t InstanceSize;
    //實例對象內(nèi)存對齊方式
    uint16_t InstanceAlignMask;
    //運行時保留字段
    uint16_t Reserved;
    //類的內(nèi)存大小
    uint32_t ClassSize;
    //類的內(nèi)存首地址
    uint32_t ClassAddressPoint;
  ...
}
  • 當(dāng)前類返回的實際類型是 TargetClassMetadata,而TargetMetaData中只有一個屬性kind,TargetAnyClassMetaData中有3個屬性崭篡,分別是kind挪哄, superclass,cacheData
  • 當(dāng)前Class在內(nèi)存中所存放的屬性由 TargetClassMetadata屬性 + TargetAnyClassMetaData屬性 + TargetMetaData屬性 構(gòu)成媚送,所以得出的metadata的數(shù)據(jù)結(jié)構(gòu)體如下所示
struct swift_class_t: NSObject{
    void *kind;//相當(dāng)于OC中的isa中燥,kind的實際類型是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;
    ...
}

三寇甸、分析思路整理

四塘偎、swif與OC 類的結(jié)構(gòu)對比

  • 實例對象 & 類

    • OC中的實例對象本質(zhì)是結(jié)構(gòu)體疗涉,是通過底層的objc_object模板創(chuàng)建,類是繼承自objc_class

    • Swift中的實例對象本質(zhì)也是結(jié)構(gòu)體吟秩,類型是HeapObject咱扣,比OC多了一個refCounts

  • 引用計數(shù)

    • OC中的ARC維護(hù)的是散列表

    • Swift中的ARC是對象內(nèi)部有一個refCounts屬性

  • 方法列表
    • OC中的方法存儲在objc_class結(jié)構(gòu)體class_rw_tmethodList

    • swift中的方法存儲在 metadata 元數(shù)據(jù)中

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涵防,隨后出現(xiàn)的幾起案子闹伪,更是在濱河造成了極大的恐慌,老刑警劉巖壮池,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偏瓤,死亡現(xiàn)場離奇詭異,居然都是意外死亡椰憋,警方通過查閱死者的電腦和手機厅克,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橙依,“玉大人证舟,你說我怎么就攤上這事〈捌铮” “怎么了女责?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長创译。 經(jīng)常有香客問我抵知,道長,這世上最難降的妖魔是什么软族? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任辛藻,我火速辦了婚禮,結(jié)果婚禮上互订,老公的妹妹穿的比我還像新娘吱肌。我一直安慰自己,他們只是感情好仰禽,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布氮墨。 她就那樣靜靜地躺著,像睡著了一般吐葵。 火紅的嫁衣襯著肌膚如雪规揪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天温峭,我揣著相機與錄音猛铅,去河邊找鬼。 笑死凤藏,一個胖子當(dāng)著我的面吹牛奸忽,可吹牛的內(nèi)容都是我干的堕伪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼栗菜,長吁一口氣:“原來是場噩夢啊……” “哼欠雌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疙筹,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤富俄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后而咆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霍比,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年暴备,在試婚紗的時候發(fā)現(xiàn)自己被綠了桂塞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡馍驯,死狀恐怖阁危,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汰瘫,我是刑警寧澤狂打,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站混弥,受9級特大地震影響趴乡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晾捏,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哀托。 院中可真熱鬧,春花似錦仓手、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽添坊。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雨女,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工戚篙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留溺职,地道東北人岔擂。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像浪耘,于是被迫代替她去往敵國和親乱灵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348