1.類與結(jié)構(gòu)體的異同
主要的相同點(diǎn):
- 定義存儲(chǔ)值的屬性
- 定義方法
- 定義下標(biāo)以使用下標(biāo)語(yǔ)法提供對(duì)其值的訪問(wèn)(點(diǎn)語(yǔ)法訪問(wèn)值)
- 定義初始化器
- 使用extension來(lái)拓展功能
- 遵循協(xié)議來(lái)提供某種功能
主要的不同點(diǎn):
- 類有繼承特性,而結(jié)構(gòu)體沒(méi)有
- 類型轉(zhuǎn)換使你能夠在運(yùn)行時(shí)檢查和解釋類實(shí)例的類型(Mirro)
- 類有析構(gòu)函數(shù)來(lái)釋放其分配的資源(deinit)
- 引用計(jì)數(shù)允許對(duì)一個(gè)類實(shí)例有多個(gè)引用
對(duì)于類與結(jié)構(gòu)體我們區(qū)分的第一件事就是:
類是引用類型
。也就意味著一個(gè)類類型的變量并不直接存儲(chǔ)具體的實(shí)例對(duì)象,是對(duì)當(dāng)前存儲(chǔ)具體實(shí)例內(nèi)存地址的引用。
這里借助2個(gè)指令來(lái)查看當(dāng)前變量的內(nèi)存結(jié)構(gòu)
po : p和po的區(qū)別在于使用po只會(huì)輸出對(duì)應(yīng)的值,而p則會(huì)輸出返回值的類型以及命令結(jié)果的引用名。
x/8g : 讀取內(nèi)存中的值(8g:8字節(jié)格式輸出)
class LGTeacher{
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
var t = LGTeacher(age: 18, name: "Kody")
var t1 = t
print("end")
(lldb) po t
<LGTeacher: 0x10070e630>
(lldb) po t1
<LGTeacher: 0x10070e630>
(lldb) x/8g 0x10070e630
0x10070e630: 0x0000000100008180 0x0000000600000003
0x10070e640: 0x0000000000000012 0x0000000079646f4b
0x10070e650: 0xe400000000000000 0x000000000000005f
0x10070e660: 0x0000000000000000 0x0000000000000000
(lldb) po withUnsafePointer(to: &t, {print($0)})
0x0000000100008218
0 elements
(lldb) po withUnsafePointer(to: &t1, {print($0)})
0x0000000100008220
0 elements
(lldb)
withUnsafePointer : 獲取變量的內(nèi)存地址
t和t1剛好差了8個(gè)字節(jié),其實(shí)這8個(gè)字節(jié)存放的就是實(shí)例對(duì)象的內(nèi)存地址
對(duì)于x/8g 0x100506400結(jié)果的解釋
第一個(gè)8字節(jié)0x0000000100008180,毫無(wú)疑問(wèn),metadata(類似于OC中的isa)
第二個(gè)8字節(jié)0x0000000600000003,這里我還不是很清楚,1個(gè)存6一個(gè)存3,待后續(xù)了解后補(bǔ)充
第三個(gè)8字節(jié)0x0000000000000012,很明顯低位的4字節(jié)存了18(age這個(gè)字段)
第四個(gè)8字節(jié)0x0000000079646f4b,低位的4字節(jié)存放了"Kody"所對(duì)應(yīng)的ASCII。
iOS為小端模式,從右邊開(kāi)始讀。0x4b-75-K;0x6f-111-o ;0x64-100-d;0x79-121-y
swift中有引用類型,就有值類型,最典型的就是
Struct
,結(jié)構(gòu)體的定義也非常簡(jiǎn)單,相比較類類型的變量中存儲(chǔ)的是地址,那么值類型存儲(chǔ)就是具體的實(shí)例(或者說(shuō)具體的值)
struct LGStudent{
var age: Int
var name: String
}
var s = LGStudent(age:18, name:"kody")
var s1 = s
print("end")
(lldb) po s
? LGStudent
- age : 18
- name : "kody"
(lldb) po s1
? LGStudent
- age : 18
- name : "kody"
(lldb)
??其實(shí)引用類型就相當(dāng)于在線的Excel,當(dāng)我們把這個(gè)鏈接共享給別人的時(shí)候,別人的修改我們是能夠看到的;值類型就相當(dāng)于本地的Excel,當(dāng)我們把本地的Excel傳遞給別人的時(shí)候,就相當(dāng)于重新復(fù)制了一份給別人,因此他們對(duì)內(nèi)容的修改我們是無(wú)法感知的。
??另外引用類型和值類型還有一個(gè)最直觀的區(qū)別就是存儲(chǔ)的位置不同:一般情況,值類型存儲(chǔ)在棧上,引用類型在堆上
。
??首先我們對(duì)內(nèi)存區(qū)域來(lái)一個(gè)基本概念的認(rèn)知,大家看下面這張圖
棧區(qū)(stack):局部變量和函數(shù)運(yùn)行過(guò)程中的上下文
//test是不是一個(gè)函數(shù)
func test() {
//我們?cè)诤瘮?shù)內(nèi)部聲明的age變量是不是就是一個(gè)局部變量
var age: Int = 10
print(age)
}
test()
(lldb) po withUnsafePointer(to: &age, {print($0)})
0x00007ffeefbff468
0 elements
(lldb) cat address 0x00007ffeefbff468
address:0x00007ffeefbff468, stack address (SP: 0x7ffeefbff440 FP: 0x7ffeefbff470) swiftTest.test() -> ()
(lldb)
堆區(qū)(Heap):存儲(chǔ)所有對(duì)象
全局區(qū)(Global):存儲(chǔ)全局變量;常量;代碼區(qū)
Segment&Section:Mach-o
文件有多個(gè)段(Segement),每個(gè)段有不同的功能,然后每個(gè)段又分為很多小的Section
TEXT.text : 機(jī)器碼
TEXT.cString : 硬編碼的字符串
TEXT.const : 初始化過(guò)的常量
DATA.data : 初始化過(guò)的可變的(靜態(tài)/全局)變量
DATA.const : 沒(méi)有初始化過(guò)的常量
DATA.bss : 沒(méi)有初始化的(靜態(tài)/全局)變量
DATA.common : 沒(méi)有初始化過(guò)的符號(hào)聲明
int age = 10;
int a;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
char *p = "LG";
NSLog(@"Hello, World!");
}
return 0;
}
(lldb) po &age
0x0000000100008020
(lldb) cat address 0x0000000100008020
address:0x0000000100008020, 8age <+0> , External: NO ocTest.__DATA.__data +8
(lldb) po &a
0x0000000100008024
(lldb) cat address 0x0000000100008024
address:0x0000000100008024, 0a <+0> , External: NO ocTest.__DATA.__common +0
(lldb) cat address 0x00007ffeefbff498
address:0x00007ffeefbff498, stack address (SP: 0x7ffeefbff490 FP: 0x7ffeefbff4b0) main
(lldb) x/8g 0x00007ffeefbff498
0x7ffeefbff498: 0x0000000100003f9e 0x00007ffeefbff4d0
0x7ffeefbff4a8: 0x0000000000000001 0x00007ffeefbff4c0
0x7ffeefbff4b8: 0x00007fff203abf3d 0x0000000000000000
0x7ffeefbff4c8: 0x0000000000000001 0x00007ffeefbff6b8
(lldb) cat address 0x0000000100003f9e
address:0x0000000100003f9e, 0ocTest.__TEXT.__cstring +0
(lldb)
我們來(lái)看例子
struct LGTeacher{
var age = 18
var name = "Kody"
}
func test(){
var t = LGTeacher()
print("end")
}
test()
frame varibale -L xxx
查看當(dāng)前變量的地址
(lldb) frame variable -L t
0x00007ffeefbff450: (swiftTest.LGTeacher) t = {
0x00007ffeefbff450: age = 18
0x00007ffeefbff458: name = "Kody"
}
(lldb) x/8g 0x00007ffeefbff450
0x7ffeefbff450: 0x0000000000000012 0x0000000079646f4b
0x7ffeefbff460: 0xe400000000000000 0x0000000000000000
0x7ffeefbff470: 0x00007ffeefbff490 0x0000000100003644
0x7ffeefbff480: 0x00007ffeefbff4b0 0x0000000100015025
(lldb)
當(dāng)運(yùn)行至var t = LGTeacher()
,棧指針會(huì)向下移動(dòng)24字節(jié)內(nèi)存空間,然后把a(bǔ)ge和name拷貝到分配好的內(nèi)存空間上。作用域完成后,棧指針移動(dòng)還原,銷毀棧內(nèi)存
疑問(wèn):如果說(shuō)當(dāng)前結(jié)構(gòu)體中有一個(gè)引用類型,會(huì)不會(huì)改變結(jié)構(gòu)體的位置?
class LGPerson {
var age = 18
var name = "LGMan"
}
struct LGTeacher{
var age = 18
var name = "Kody"
var p = LGPerson()
}
func test(){
var t = LGTeacher()
print("end")
}
test()
(lldb) frame variable -L t
0x00007ffeefbff450: (swiftTest.LGTeacher) t = {
0x00007ffeefbff450: age = 18
0x00007ffeefbff458: name = "Kody"
scalar: p = 0x000000010076f140 {
0x000000010076f150: age = 18
0x000000010076f158: name = "LGMan"
}
}
(lldb) x/8g 0x00007ffeefbff450
0x7ffeefbff450: 0x0000000000000012 0x0000000079646f4b
0x7ffeefbff460: 0xe400000000000000 0x000000010076f140
0x7ffeefbff470: 0x00007ffeefbff490 0x0000000100002cb4
0x7ffeefbff480: 0x00007ffeefbff4b0 0x0000000100019025
我們可以發(fā)現(xiàn),當(dāng)結(jié)構(gòu)體存在引用類型時(shí),結(jié)構(gòu)體依然存放在棧上,引用類型依然會(huì)在堆上創(chuàng)建,并且棧上存放的是引用類型的地址0x000000010076f140
,指向堆空間。
因此結(jié)構(gòu)體中是否有引用類型并不影響結(jié)構(gòu)體的存儲(chǔ)位置。
當(dāng)我們把struct改為class
class LGTeacher{
var age = 18
var name = "Kody"
}
func test(){
//棧上:分配8字節(jié)大小存儲(chǔ)實(shí)例引用類型
//堆上:尋找合適的內(nèi)存區(qū)域
//value的值拷貝到堆區(qū)內(nèi)存空間中
//把棧上的內(nèi)存地址指向當(dāng)前堆區(qū)
var t = LGTeacher()
print("end")
}
test()
2.類與結(jié)構(gòu)體的選擇
- 1.引入github上StructVsClassPerformance這個(gè)案例來(lái)直觀的測(cè)試當(dāng)前結(jié)構(gòu)體和類的時(shí)間分配
class Tests {
static func runTests() {
print("Running tests")
measure("class (1 field)") {
var x = IntClass(0)
for _ in 1...10000000 {
x = x + IntClass(1)
}
}
measure("struct (1 field)") {
var x = IntStruct(0)
for _ in 1...10000000 {
x = x + IntStruct(1)
}
}
measure("class (10 fields)") {
var x = Int10Class(0)
for _ in 1...10000000 {
x = x + Int10Class(1)
}
}
measure("struct (10 fields)") {
var x = Int10Struct(0)
for _ in 1...10000000 {
x = x + Int10Struct(1)
}
}
}
static private func measure(_ name: String, block: @escaping () -> ()) {
print()
print("\(name)")
let t0 = CACurrentMediaTime()
block()
let dt = CACurrentMediaTime() - t0
print("\(dt)")
}
}
統(tǒng)計(jì)1/10個(gè)變量的class和struct創(chuàng)建10000000次所需要的時(shí)間。
Running tests
class (1 field)
8.673463547999745
struct (1 field)
4.282427998999992
class (10 fields)
7.942918924999503
struct (10 fields)
4.6826105930003905
結(jié)果很明顯,無(wú)論是1個(gè)變量還是10個(gè)變量,struct的性能都遠(yuǎn)高于class。
一般來(lái)說(shuō),盡可能優(yōu)先選用結(jié)構(gòu)體,結(jié)構(gòu)體在棧上線程安全,在進(jìn)行內(nèi)存分配的過(guò)程中也是比堆區(qū)內(nèi)存分配快得多
- 2.使用官方案例一
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [String : UIImage]()
func makeBalloon(_ color: Color, _ orientation: Orientation, _ tail: Tail) -> UIImage {
let key = "\(color):\(orientation):\(tail)"
if let image = cache[key] {
return image
}
...
}
分析:cache中使用string作為key值是有問(wèn)題的。方法中的key(字符串)是存放在堆區(qū)上的,因此每次執(zhí)行到該方法時(shí),雖然有字典的緩存,緩存雖然命中,但是仍然會(huì)進(jìn)行不停的堆內(nèi)存的分配與銷毀。很顯然這個(gè)效率是無(wú)法接受的(此時(shí)該需求是聊天頁(yè)面上的氣泡)
那么我們應(yīng)該使用struct作為cache的key值來(lái)提高效率
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [Balloon : UIImage]()
func makeBalloon(_ balloon: Balloon) -> UIImage {
if let image = cache[balloon] {
return image
}
...
}
struct Balloon: Hashable {
var color: Color
var orientation: Orientation
var tail: Tail
}
此時(shí)就沒(méi)有了堆上的內(nèi)存的分配與銷毀
,對(duì)于我們當(dāng)前的代碼執(zhí)行效率就非常高了
- 3.使用官方案例二
struct Attachment {
let fileURL: URL
let uuid: String
let mineType: String
init?(fileURL: URL, uuid: String, mineType: String) {
guard mineType.isMineType
else { return nil }
self.fileURL = fileURL
self.uuid = uuid
self.mineType = mineType
}
}
此時(shí)出現(xiàn)了3個(gè)堆區(qū)變量,在分配內(nèi)存及引用計(jì)數(shù)層面上消耗內(nèi)存比較大,因此需要優(yōu)化
struct Attachment {
let fileURL: URL
let uuid: UUID
let mineType: MineType
init?(fileURL: URL, uuid: UUID, mineType: MineType) {
guard mineType.isMineType
else { return nil }
self.fileURL = fileURL
self.uuid = uuid
self.mineType = mineType
}
}
enum MineType: String{
case jpeg = "image/jpeg"
....
}
將uuid優(yōu)化為UUID值類型,將mineType優(yōu)化為enum值類型
3.初始化器
類初始器
class LGTeacher{
var age: Int
var name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
}
var t = LGTeacher(18, "Kody")
當(dāng)前的類編譯器不會(huì)自動(dòng)提供成員初始化器
struct初始化器
struct LGTeacher {
var age: Int
var name: String
}
var t = LGTeacher(age: 18, name: "Kody")
當(dāng)前的結(jié)構(gòu)體編譯器會(huì)自動(dòng)提供成員初始化器(前提是我們沒(méi)有指定初始化器)
便捷初始化器
class LGTeacher{
var age: Int
var name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
convenience init() {
self.init(18, "Kody")
}
}
class LGPerson: LGTeacher {
var subjectName: String
init(_ subjectName: String) {
//調(diào)用父類初始化器之前,自身類的屬性必須初始化完成
self.subjectName = subjectName
super.init(18, "Kody")
}
}
var t = LGPerson("a")
這里我們記住:
- 指定初始化器必須保證在向上委托給父類初始化器之前,其所在類引入的所有屬性都要初始化完成。
- 指定初始化器必須先向上委托父類初始化器,然后才能為繼承的屬性設(shè)置新值。如果不這樣做,指定初始化器賦予的新值將被父類中的初始化器所覆蓋
- 便捷初始化器必須先委托同類中的其它初始化器,然后再為任意屬性賦新值(包括 同類里定義的屬性)。如果沒(méi)這么做,便捷構(gòu)初始化器賦予的新值將被自己類中其它指定初始化器所覆蓋。
- 初始化器在第一階段初始化完成之前,不能調(diào)用任何實(shí)例方法、不能讀取任何實(shí)例屬性的值,也不能引用 self 作為值。
-
可失敗初始化器
意味著當(dāng)前因?yàn)閰?shù)不合法或外部條件不滿足,存在初始化失敗的情況。這種Swift中可失敗初始化器寫return nil語(yǔ)句,來(lái)表明可失敗初始化器在何種情況下回觸發(fā)初始化失敗
class LGTeacher{
var age: Int
var name: String
init?(_ age: Int, _ name: String) {
if age < 18 { return nil}
self.age = age
self.name = name
}
convenience init?() {
self.init(18, "Kody")
}
}
var t = LGTeacher(17, "Kody")
print("end")
-
必要初始化器
在類的初始化器前添加required
修飾符來(lái)表明所有該類的子類如果要自定義初始化器
的話都必須實(shí)現(xiàn)該初始化器。
class LGPerson {
var age: Int
var name: String
required init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
convenience init() {
self.init(18, "Kody")
}
}
class LGTeacher: LGPerson {
var subjectName: String = ""
//自定義初始化器,必須實(shí)現(xiàn)required init(){}
init(_ subjectName: String) {
super.init(18, "Kody")
self.subjectName = subjectName
}
required init(_ age: Int, _ name: String) {
super.init(age, name)
}
//當(dāng)然也可以添加一個(gè)便捷初始化器來(lái)完成這個(gè)功能,此時(shí)就不需要實(shí)現(xiàn)required init(){}
convenience init(_ age: Int, _ name: String, _ subjectName: String) {
self.init(age, name)
self.subjectName = subjectName
}
}
var t = LGTeacher(17, "kody")
print("end")
4.類的生命周期
iOS開(kāi)發(fā)的語(yǔ)言不管是OC還是Swift后端都是通過(guò)LLVM進(jìn)行編譯的,如圖所示
OC通過(guò)clang編譯器,編譯成IR,然后再生成可執(zhí)行文件.o(這里也就是我們的機(jī)器碼)
Swift則是通過(guò)Swift編譯器編譯成iR,然后再生成可執(zhí)行文件。
-
Parse
,語(yǔ)法分析,解析成抽象語(yǔ)法樹(shù) -
Sema
,語(yǔ)義分析。比如說(shuō)當(dāng)前的類型檢查是否正確、是否安全 -
SILGen
,降級(jí)變成SILGen。SIL全稱為Swift Interminal Language(Swift中間語(yǔ)言)
-
Raw SIL
,原生的SIL,沒(méi)有開(kāi)啟優(yōu)化選項(xiàng) -
SILOpt Canonical SIL
,優(yōu)化后的SIL。優(yōu)化了一些額外的代碼 -
IRGen和LLVM IR
,由LLVM降級(jí)成IR -
Machine Code
,由后端代碼變?yōu)闄C(jī)器碼(x86、arm64、...)
// 分析輸出AST
swiftc main.swift -dump-parse
// 分析并且檢查類型輸出AST
swiftc main.swift -dump-ast
// 生成中間體語(yǔ)言(SIL),未優(yōu)化
swiftc main.swift -emit-silgen
// 生成中間體語(yǔ)言(SIL),優(yōu)化后的
swiftc main.swift -emit-sil
// 生成LLVM中間體語(yǔ)言 (.ll文件)
swiftc main.swift -emit-ir
// 生成LLVM中間體語(yǔ)言 (.bc文件)
swiftc main.swift -emit-bc
// 生成匯編
swiftc main.swift -emit-assembly
// 編譯生成可執(zhí)行.out文件
swiftc -o main.o main.swift
將main.swift輸出成優(yōu)化過(guò)后的SIL
class LGTeacher {
var age = 18
var name = "LG"
}
var t = LGTeacher()
sil_stage canonical
import Builtin
import Swift
import SwiftShims
import Foundation
class LGTeacher {
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
@_hasStorage @_hasInitialValue var t: LGTeacher { get set }
// t
sil_global hidden @$s4main1tAA9LGTeacherCvp : $LGTeacher
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
//alloc_global,分配一個(gè)全局變量
alloc_global @$s4main1tAA9LGTeacherCvp // id: %2
//拿到全局變量的地址給到%3寄存器
%3 = global_addr @$s4main1tAA9LGTeacherCvp : $*LGTeacher // user: %7
//%4寄存器存放LGTeacher元類型
%4 = metatype $@thick LGTeacher.Type // user: %6
// function_ref LGTeacher.__allocating_init()
// 定義一個(gè)方法引用給%5寄存器(函數(shù)的指針地址給到%5)
%5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
// 執(zhí)行%5函數(shù)指針,參數(shù)為%4,并且把函數(shù)的返回值給到%6寄存器
%6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %7
//將%6存儲(chǔ)到%3,實(shí)例變量的內(nèi)存地址存到全局變量
store %6 to %3 : $*LGTeacher // id: %7
//定義一個(gè)Int32值,并且值為0,然后return 0。類似于OC代碼中的main
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
return %9 : $Int32 // id: %10
} // end sil function 'main'
...
對(duì)于LGTeacher
-
@_hasStorage @_hasInitialValue
表示有一個(gè)初始化過(guò)的存儲(chǔ)屬性 - 這里的LGTeacher有2個(gè)初始化過(guò)的存儲(chǔ)屬性age、name
- @objc標(biāo)記的deinit和默認(rèn)的init函數(shù)
@main:入口函數(shù),@
%0:寄存器,也可以理解成常量。一旦賦值不能修改。虛擬的,跑到設(shè)備上會(huì)使用真的寄存器
- 還原當(dāng)前的名稱,
xcrun swift-demangle
? xcrun swift-demangle s4main1tAA9LGTeacherCvp
$s4main1tAA9LGTeacherCvp ---> main.t : main.LGTeacher
找到寄存器%5中s4main9LGTeacherCACycfC
函數(shù)
// LGTeacher.__allocating_init()
sil hidden [exact_self_class] @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher {
// %0 "$metatype"
bb0(%0 : $@thick LGTeacher.Type):
%1 = alloc_ref $LGTeacher // user: %3
// function_ref LGTeacher.init()
%2 = function_ref @$s4main9LGTeacherCACycfc : $@convention(method) (@owned LGTeacher) -> @owned LGTeacher // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned LGTeacher) -> @owned LGTeacher // user: %4
return %3 : $LGTeacher // id: %4
} // end sil function '$s4main9LGTeacherCACycfC'
-
metadata
可以理解為isa -
alloc_ref
,進(jìn)入SIL文檔查詢
Allocates an object of reference type T. The object will be initialized with retain count 1; its state will be otherwise uninitialized. The optional objc attribute indicates that the object should be allocated using Objective-C's allocation methods (+allocWithZone:).
大概意思為分配一個(gè)引用類型為T的對(duì)象,并且該對(duì)象的引用計(jì)數(shù)為1。也就是說(shuō)在堆區(qū)上申請(qǐng)內(nèi)存空間。如果對(duì)象標(biāo)識(shí)為objc,將會(huì)使用Objective-C的+allocWithZone:方法申請(qǐng)內(nèi)存空間。也就是oc的初始化方法
我們通過(guò)代碼來(lái)查看2者的區(qū)別
class LGTeacher {
var age = 18
var name = "LG"
}
var t = LGTeacher()
進(jìn)入?yún)R編斷點(diǎn),callq
其實(shí)相當(dāng)于函數(shù)調(diào)用。調(diào)用LGTeacher.__allocating_init()
,進(jìn)入發(fā)現(xiàn)斷點(diǎn)跑到了LGTeacher.__allocating_init
,在這里執(zhí)行swift_allocObject
,并且執(zhí)行初始化方法LGTeacher.init
。
class LGTeacher: NSObject {
var age = 18
var name = "LG"
}
var t = LGTeacher()
執(zhí)行objc_allocWithZone
,并且使用objc_msgSend
發(fā)送init
消息
至此,已經(jīng)通過(guò)代碼來(lái)解釋SIL文檔中的alloc_ref
此時(shí),我們可以通過(guò)Swift源碼來(lái)分析_swift_allocObject_
- 進(jìn)入到
HeapObject.cpp
文件,找到swift_allocObject
函數(shù)
//metadata,元數(shù)據(jù)類型。8字節(jié)
//requiredSize,需要的字節(jié)大小
//requiredAlignmentMask,對(duì)齊掩碼。Swift對(duì)象8字節(jié)對(duì)齊,因此為7
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));
// 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);
// 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
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
void *p;
// This check also forces "default" alignment to use AlignedAlloc.
//# define MALLOC_ALIGN_MASK 15
if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
p = malloc(size);
#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;
}
swift_slowAlloc
其實(shí)就是在調(diào)用malloc
開(kāi)辟內(nèi)存空間
Swift對(duì)象內(nèi)存分配:
Object.__allocating_init(編譯器生成)
----> swift_allocObject
-----> _swift_allocObject_(HeapObject.cpp)
----> swift_slowAlloc(Heap.cpp)
----> malloc(Heap.cpp)
Swift對(duì)象的內(nèi)存結(jié)構(gòu)HeapObject
(OC objc_object
),有2個(gè)屬性:metadata
,refCount
,默認(rèn)占用16字節(jié)大小。
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *__ptrauth_objc_isa_pointer metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
#ifndef __swift__
HeapObject() = default;
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
// Initialize a HeapObject header for an immortal object
constexpr HeapObject(HeapMetadata const *newMetadata,
InlineRefCounts::Immortal_t immortal)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Immortal)
{ }
#ifndef NDEBUG
void dump() const SWIFT_USED;
#endif
#endif // __swift__
};
我們已經(jīng)分析了實(shí)例對(duì)象的內(nèi)存結(jié)構(gòu),接下來(lái)分析HeapMetadta
,也就是metadata
//其實(shí)就是TargetHeapMetadata類型,起了個(gè)別名
using HeapMetadata = TargetHeapMetadata<InProcess>;
點(diǎn)擊進(jìn)入TargetHeapMetaData
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
};
TargetHeapMetaData繼承自TargetMetaData
初始化方法中,如果是純swift類,傳入的參數(shù)就是MetadataKind。如果swift與OBJC交互的話,傳入的參數(shù)就是isa。
關(guān)于MetadataKind的定義
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 |
此時(shí)我們繼續(xù)進(jìn)入TargetMetadata
探究,其實(shí)已經(jīng)進(jìn)入了一個(gè)瓶頸了。繼續(xù)查看分析這個(gè)結(jié)構(gòu)體
getTypeContextDescriptor() const {
switch (getKind()) {
case MetadataKind::Class: {
const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);
if (!cls->isTypeMetadata())
return nullptr;
if (cls->isArtificialSubclass())
return nullptr;
return cls->getDescription();
}
case MetadataKind::Struct:
case MetadataKind::Enum:
case MetadataKind::Optional:
return static_cast<const TargetValueMetadata<Runtime> *>(this)
->Description;
case MetadataKind::ForeignClass:
return static_cast<const TargetForeignClassMetadata<Runtime> *>(this)
->Description;
default:
return nullptr;
}
}
根據(jù)不同的MetadataKind
類型來(lái)區(qū)分不同的數(shù)據(jù)類型。所以MetadataKind
是所有類型元類的最終基類
const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);
當(dāng)我的metadata是class類型的時(shí)候,將當(dāng)前指針強(qiáng)轉(zhuǎn)為TargetClassMetadata
類型
進(jìn)入TargetClassMetadata
中有以下代碼
constexpr TargetClassMetadata(const TargetAnyClassMetadata<Runtime> &base,
ClassFlags flags,
ClassIVarDestroyer *ivarDestroyer,
StoredPointer size, StoredPointer addressPoint,
StoredPointer alignMask,
StoredPointer classSize, StoredPointer classAddressPoint)
/// Swift-specific class flags.
ClassFlags Flags;
/// The address point of instances of this type.
uint32_t InstanceAddressPoint;
/// The required size of instances of this type.
/// 'InstanceAddressPoint' bytes go before the address point;
/// 'InstanceSize - InstanceAddressPoint' bytes go after it.
uint32_t InstanceSize;
/// The alignment mask of the address point of instances of this type.
uint16_t InstanceAlignMask;
/// Reserved for runtime use.
uint16_t Reserved;
/// The total size of the class object, including prefix and suffix
/// extents.
uint32_t ClassSize;
/// The offset of the address point within the class object.
uint32_t ClassAddressPoint;
進(jìn)入TargetClassMetadata
父類TargetAnyClassMetadata
中有以下代碼
TargetSignedPointer<Runtime, const TargetClassMetadata<Runtime> *
__ptrauth_swift_objc_superclass>
Superclass;
TargetPointer<Runtime, void> CacheData[2];
StoredSize Data;
看了這2個(gè)類,參考o(jì)bjc_class我們可以大膽的猜測(cè)這就是Swift類的數(shù)據(jù)結(jié)構(gòu)
通過(guò)源碼總結(jié)的Swift類的數(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
}
通過(guò)代碼來(lái)驗(yàn)證
struct HeapObject {
var metadata: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
class LGTeacher {
var age = 18
var name = "LG"
}
var t = LGTeacher()
//將t的指針重新綁定到HeapObject
//獲取實(shí)例對(duì)象的指針
var objcRawPtr = Unmanaged.passUnretained(t).toOpaque()
//重新綁定到HeapObject
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objcPtr.pointee)
print("end")
執(zhí)行結(jié)果
HeapObject(metadata: 0x00000001000081a0, refCount1: 3, refCount2: 0)
(lldb) x/8g 0x00000001000081a0
0x1000081a0: 0x0000000100008168 0x00007ff852c3f8b8
0x1000081b0: 0x00007ff8112eb800 0x0000803000000000
0x1000081c0: 0x000000010143f2f2 0x0000000000000002
0x1000081d0: 0x0000000700000028 0x00000010000000a8
(lldb)
- 0x0000000100008168 ----> isa
那么此時(shí)的metadata
我們是否也可以還原成Metadata(源碼總結(jié)的)
結(jié)構(gòu)體呢?
struct HeapObject {
var metadata: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
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
}
class LGTeacher {
var age = 18
var name = "LG"
}
var t = LGTeacher()
//將t的指針重新綁定到HeapObject
//獲取實(shí)例對(duì)象的指針
var objcRawPtr = Unmanaged.passUnretained(t).toOpaque()
//重新綁定到HeapObject
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objcPtr.pointee)
//MemoryLayoutce,Swift中測(cè)量數(shù)據(jù)類型的大小
let metadataPtr = objcPtr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride)
print(metadataPtr.pointee)
print("end")
執(zhí)行結(jié)果
HeapObject(metadata: 0x00000001000081a8, refCount1: 3, refCount2: 0)
Metadata(kind: 4295000432, superClass: _TtCs12_SwiftObject, cacheData: (140703416891392,
140943646785536), data: 4485824738, classFlags: 2, instanceAddressPoint: 0, instanceSize: 40,
instanceAlignmentMask: 7, reserved: 0, classSize: 168, classAddressPoint: 16, typeDescriptor:
0x0000000100003c4c, iVarDestroyer: 0x0000000000000000)
(lldb)
-
superClass: _TtCs12_SwiftObject
,父類為_TtCs12_SwiftObject
-
instanceSize: 40
,實(shí)例大小為16(metadata+refCount)+24(age+name(8+8)) -
instanceAlignmentMask: 7
,8字節(jié)對(duì)齊掩碼
至此,通過(guò)代碼已經(jīng)證明了MetaData的數(shù)據(jù)結(jié)構(gòu)