第一節(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)行編譯的,如下圖所示:

可以看到:
OC通過clang編譯器,編譯成IR,然后再生成可執(zhí)行文件.o(這里也就是我們的機(jī)械碼)
Swift則是通過Swift編譯器編譯成IR,然后再生成可執(zhí)行文件。
我們?cè)賮砜匆幌?,一個(gè)swift文件的編譯過程都經(jīng)歷了什么步驟:

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

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

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

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

- 在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ā)生的事情:
- _allocating_init --> swift_allocObject --> _ swift_allocObject --> swift_slowAlloc --> Malloc
- 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é)。 - 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;
從TargetHeapMetadata、TargetMetaData定義中,均可以看出初始化方法中參數(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)證

po metadata->getKind(),得到其kind是Class
po metadata->getClassObject()、x/8g 0x0000000110efdc70,這個(gè)地址中存儲(chǔ)的是元數(shù)據(jù)信息!
所以,TargetMetadata 和 TargetClassMetadata 本質(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, superclass,cacheData
當(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文件中可以看出來

特征:會(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ā)屬性觀察者

注:子類與父類的調(diào)用順序如上圖:子父父子(聽到一個(gè)挺恰當(dāng)?shù)谋扔鳎瑑鹤訌母赣H那繼承了遺產(chǎn),但是想變賣的時(shí)候還是需要跟父親說一聲,父親操作一番之后再告訴兒子進(jìn)行變賣)
-
延遲存儲(chǔ)屬性:
- 使用lazy修飾的存儲(chǔ)屬性
class HZMTeacher{ lazy var age: Int = 10 }-
延遲屬性必須有一個(gè)默認(rèn)的初始值
懶加載01.png
-
延遲存儲(chǔ)在第一次訪問的時(shí)候才被賦值
懶加載02.png
從圖中我們可以看出,第一個(gè)斷點(diǎn)的時(shí)候第一次訪問時(shí)值為0 第二個(gè)斷點(diǎn)的時(shí)候代表訪問過后,進(jìn)行了賦值
- 延遲存儲(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ì)走一遍賦值,并不能保證屬性只初始化了一次
-
延遲存儲(chǔ)屬性對(duì)實(shí)例對(duì)象大小的影響
懶加載04.png
從打印類的內(nèi)存大小可以看出,使用懶加載后內(nèi)存變大(空間換時(shí)間)
- 類型屬性:
類型屬性屬于這個(gè)類的本身,不管有多少個(gè)實(shí)例,類型屬性只有一份,我們使用static來修飾一個(gè)類型屬性
- 使用
static修飾,且是全局變量 - 類型屬性必須有一個(gè)
默認(rèn)初始值 - 類型屬性只會(huì)被
初始化一次




