一钝满、類與結構體的異同
-
相同點
- 定義存儲值的屬性
- 定義方法
- 定義初始化器
- 定義下標,并使用下表語法訪問其值
- 使用extension來擴展功能
- 遵循協(xié)議來提供某種功能
-
不同點
- 類有繼承质况,而結構體沒有
- 類型轉換使得您能夠在運行時檢查和解釋類實例的類型
- 類有析構函數(shù)來釋放其分配的資源
- 類有引用計數(shù)記錄對一個是咧的引用次數(shù)
類是引用類型肝谭。意味著仆救,一個類型的變量并不直接存儲具體的實例對象,而是存儲具體實例對象的內(nèi)存地址膳沽。
// 類的時候
class PSYModel{
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
var t = PSYModel.init(age: 18, name: "psy")
var t1 = t
print("end")
可以通過lldb命令:cat address 0x100570af0
查看其實存取的區(qū)域汗菜,可以發(fā)現(xiàn)是在heap(堆)區(qū)。
結構體是值類型(值類型還有enum枚舉等)挑社。意味著陨界,一個結構體類型的存儲是具體的實例值,而不是像引用類型存儲的是具體實例對象的內(nèi)存地址痛阻。
// 結構體的時候
struct PSYModel{
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
var t = PSYModel.init(age: 18, name: "psy")
var t1 = t
t.name = "俏~"
print("end")
由上面預演的可知菌瘪,打印t
和t1
都是直接打印出結構體的值,并且阱当,t1
是t
的一個副本俏扩,類似于本地化的一份,修改任何一個實例化的值對另一個實例沒有影響弊添。
關于內(nèi)存可以先了解一下 內(nèi)存管理的五大區(qū) 這篇文章录淡,以下主要驗證相關的各個內(nèi)存區(qū)域:
棧區(qū)(stack)
:局部變量,參數(shù)油坝,函數(shù)運行時上下文
堆區(qū)(heap)
:存儲多有對象嫉戚,由程序員/系統(tǒng)申請并由程序員/系統(tǒng)釋放
全局區(qū)(global)
:
常量區(qū)(data)
:
代碼區(qū)(text)
結構體類型中如果添加了引用類型的成員變量,則會在堆區(qū)申請空間澈圈,而原來的結構體實例存儲的位置不變彬檀,只是引用類型的成員變量區(qū)域存儲的是指向實例的堆區(qū)的內(nèi)存指針,如下圖:
所以在結構體中盡量不要添加引用類型成員變量瞬女,因為那樣會涉及到堆內(nèi)存的申請和釋放窍帝,影響性能。
二诽偷、初始化器
1. 執(zhí)行初始化器&便捷初始化器
類編譯器默認不會自動提供成員初始化器坤学,也就是如果類中有兩個成員疯坤,如果沒有初始化,則會報錯拥峦。但是類中在沒有成員的時候贴膘,會默認有一個init(){}
初始化器卖子。
結構體默認自動提供初始化器
Swift中創(chuàng)建類的實例時必須為所有的存儲屬性設置初始值略号,因為swift會根據(jù)類型確定變量的類型或者根據(jù)初始值推斷變量類型,這就是類型安全洋闽。所以類必須提供指定初始化器玄柠,也可以根據(jù)需要提供便捷初始化器(在初始化前面加上convenience
關鍵字,便捷初始化器必須從相同的類中調用指定初始化器)诫舅。
便捷初始化器定義有一些規(guī)則羽利,加上convenience
關鍵字之后,需要嚴格控制初始化器的創(chuàng)建 規(guī)則:
指定初始化器必須保證在向上委托給父類初始化器之前刊懈,其所在類引入的所有屬性都要初始化完成这弧。
指定初始化器必須先向上委托父類初始化器,然后才能為繼承的屬性設置新值虚汛。如果不這樣做匾浪,指定初始化器賦予的新值將被父類中的初始化器所覆蓋
便捷初始化器必須先委托同類中的其它初始化器,然后再為任意屬性賦新值(包括 同類里定義的屬性)卷哩。如果沒這么做蛋辈,便捷構初始化器賦予的新值將被自己類中其它指定初始化器所覆蓋。
初始化器在第一階段初始化完成之前将谊,不能調用任何實例方法冷溶、不能讀取任何實例屬性的值,也不能引用 self 作為值尊浓。
2. 可失敗初始化器
可失敗初始化器 : 可以根據(jù)業(yè)務需要逞频,根據(jù)參數(shù)d的決定是否初始化失敗,也就是
return nil
三栋齿、類的生命周期
1. Swift的編譯流程
iOS開發(fā)語言OC和Swift后端都是通過LLVM進行編譯最終生成可執(zhí)行文件虏劲;
OC的編譯是通過clang編譯器,生成LLVM中間IR代碼褒颈,最后由后端生成可執(zhí)行文件.o柒巫;
Swift是通過Swift編譯器編譯生成,sil文件谷丸,再生成LLVM的可操作性的IR中間代碼堡掏,然后再生成可執(zhí)行文件。在這個過程中刨疼,區(qū)別就是前段編譯器不一樣泉唁,多了一個步驟是生成了sil文件鹅龄,一下是編譯流程圖:
其生成步驟可以單步在終端輸入特定的指令生成中間的代碼,指令集如下:
// 分析輸出AST
swiftc main.swift -dump-parse
// 分析并且檢查類型輸出AST
swiftc main.swift -dump-ast
// 生成中間體語言(SIL)亭畜,未優(yōu)化
swiftc main.swift -emit-silgen
// 生成中間體語言(SIL)扮休,優(yōu)化后的
swiftc main.swift -emit-sil
// 生成LLVM中間體語言 (.ll文件)
swiftc main.swift -emit-ir
// 生成LLVM中間體語言 (.bc文件)
swiftc main.swift -emit-bc
// 生成匯編
swiftc main.swift -emit-assembly
// 編譯生成可執(zhí)行.out文件
swiftc -o main.o main.swift
2. Swift特有的(區(qū)別于OC)SIL代碼
生成SIL文件的時候可以執(zhí)行輸出main.c文件,方便Xcode查看拴鸵,指令如下:swiftc main.swift -emit-sil -o main.c
SIL的基本語法:SIL官方基本語法
@main: 入口函數(shù)玷坠, @作為標識符
%0、%1劲藐、%2....: 可理解為虛擬的寄存器八堡,類似于日常開發(fā)中的常量,一旦賦值不可修改聘芜,最后如果跑到設備商會使用真的寄存器
% 局部標識
alloca 開辟空間
align 內(nèi)存對齊
i32 32位兄渺, 4字節(jié)
store 寫入內(nèi)存
load 讀取數(shù)據(jù)
call 調用函數(shù)
ret 返回
s4main1tAA8PSYModelCvp
這種是混寫的,可以使用終端使用命令還原:xcrun swift-demangle s4main1tAA8PSYModelCvp
load: 讀取數(shù)據(jù)
sil_global:標記變量為全局變量
hidden: 標記只針對同一個Swift模塊中的對象可見
alloc_global: 開辟全局變量的內(nèi)存
global_addr: 獲取全局變量的地址
ref_element_addr: 獲取元素地址
init_existential_addr: 指令會生成 Existential Container 結構, 包裹著實例變量和協(xié)議對應的 PWT
destroy_addr
bb0 / bb1 ... : basic block 數(shù)字,表示一個代碼塊汰现,SIL中沒有分支語句挂谍,只有入口和出口
alloc_ref / dealloc_ref: 開辟/釋放內(nèi)存
function_ref: 獲取直接派發(fā)函數(shù)地址.
class_method: 通過函數(shù)表獲取方法.
witness_method: 通過 PWT 獲取對應的函數(shù)地址
objc_method : 獲取OC 方法地址
apply:調用函數(shù)
store A to B : 把A 的值存儲到B中。
begin_access / end_access: 開始瞎饲、結束訪問
[modify] / [read] / [deinit] :修改型訪問口叙、讀取型訪問、刪除型訪問
[dynamic]:動態(tài)訪問
[static]:靜態(tài)訪問
retain_value: 引用計數(shù) + 1
release_value: 引用計數(shù) - 1
metatype 獲得元類型
@thick 描述元類型代表的形式企软,是引用 對象類型或是其子類,
@thin 代表一個確切的值 類型庐扫,不需要存儲,
$ : 類型標識
%: 表示寄存器,類似局部常量仗哨,賦值后不可修改形庭。如果再需要新的寄存器,就增加寄存器編號厌漂,這樣操作有利于編譯器的優(yōu)化萨醒;后續(xù)進行降級操作 時,才會把這些帶編號的虛擬寄存器 轉換成對應體系結構的真實寄存器苇倡。
@ : SIL中所有標識符均以@符號開頭
@main 方法名字是 main
@_hasStorage 標識屬性是存儲屬性
@_hasInitialValue 標識屬性有初始值
@owned 代表函數(shù)接收者負責銷毀返回值
@convention 這個標識用于明確指定當函數(shù)調用時參數(shù)和返回值應該如何被處理
@convention(c) 表示使用C函數(shù)的方式進行調用
@convention(swift) 純Swift函數(shù)的默認調用方式
@convention(method) 柯里化的函數(shù)調用方式
@convention(witness_method) 協(xié)議方法調用其监,它等同于convention(method)滞乙,除了在處理范型類型參數(shù)時
@convention(objc_method) Objective-C方式調用
SIL代碼以及分析注釋如下:
sil_stage canonical
import Builtin
import Swift
import SwiftShims
class PSYModel {
@_hasStorage var age: Int { get set } // 有一個變量age届谈,還有set和get方法
@_hasStorage var name: String { get set } // 有一個變量name魔吐,還有set和get方法
init(age: Int, name: String) // 有一個指定初始化器
@objc deinit // 析構函數(shù),@objc標識符
}
// 一個已經(jīng)初始化的變量t
@_hasStorage @_hasInitialValue var t: PSYModel { get set }
// 一個已經(jīng)初始化的變量t1
@_hasStorage @_hasInitialValue var t1: PSYModel { get set }
// t
sil_global hidden @$s4main1tAA8PSYModelCvp : $PSYModel
// t1
sil_global hidden @$s4main2t1AA8PSYModelCvp : $PSYModel
// main 入口函數(shù)
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
// 分配一個全局變量t, s4main1tAA8PSYModelCvp是混寫之后的
alloc_global @$s4main1tAA8PSYModelCvp // id: %2
// 拿到全局變量的地址--->%3
%3 = global_addr @$s4main1tAA8PSYModelCvp : $*PSYModel // users: %15, %18
//
%4 = metatype $@thick PSYModel.Type // user: %14
%5 = integer_literal $Builtin.Int64, 18 // user: %6
%6 = struct $Int (%5 : $Builtin.Int64) // user: %14
%7 = string_literal utf8 "psy" // user: %12
%8 = integer_literal $Builtin.Word, 3 // user: %12
%9 = integer_literal $Builtin.Int1, -1 // user: %12
%10 = metatype $@thin String.Type // user: %12
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
// 函數(shù)引用String.init综慎,有三個參數(shù)
%11 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %12
// 調用String.init("psy", 3 , -1, String.Type)涣仿,得到一個字符串
%12 = apply %11(%7, %8, %9, %10) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %14
// function_ref PSYModel.__allocating_init(age:name:)
// 函數(shù)引用__allocating_init(age:name:)
%13 = function_ref @$s4main8PSYModelC3age4nameACSi_SStcfC : $@convention(method) (Int, @owned String, @thick PSYModel.Type) -> @owned PSYModel // user: %14
// 實例化一個對象調用指定初始化器,得到PSYModel對象
%14 = apply %13(%6, %12, %4) : $@convention(method) (Int, @owned String, @thick PSYModel.Type) -> @owned PSYModel // user: %15
// 將對象存儲到全局變量
store %14 to %3 : $*PSYModel // id: %15
// 在一個全局變量 t1
alloc_global @$s4main2t1AA8PSYModelCvp // id: %16
// 拿到全局變量的內(nèi)存地址
%17 = global_addr @$s4main2t1AA8PSYModelCvp : $*PSYModel // user: %19
// 開始:動態(tài)讀取t全局變量內(nèi)存
%18 = begin_access [read] [dynamic] %3 : $*PSYModel // users: %20, %19
// 地址拷貝存到t1的內(nèi)存地址
copy_addr %18 to [initialization] %17 : $*PSYModel // id: %19
// 結束:
end_access %18 : $*PSYModel // id: %20
一下分析同理。好港。愉镰。。钧汹。丈探。。拔莱。碗降。
%21 = integer_literal $Builtin.Word, 1 // user: %23
// function_ref _allocateUninitializedArray<A>(_:)
%22 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %23
%23 = apply %22<Any>(%21) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %25, %24
%24 = tuple_extract %23 : $(Array<Any>, Builtin.RawPointer), 0 // users: %43, %40
%25 = tuple_extract %23 : $(Array<Any>, Builtin.RawPointer), 1 // user: %26
%26 = pointer_to_address %25 : $Builtin.RawPointer to [strict] $*Any // user: %33
%27 = string_literal utf8 "end" // user: %32
%28 = integer_literal $Builtin.Word, 3 // user: %32
%29 = integer_literal $Builtin.Int1, -1 // user: %32
%30 = metatype $@thin String.Type // user: %32
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%31 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %32
%32 = apply %31(%27, %28, %29, %30) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %34
%33 = init_existential_addr %26 : $*Any, $String // user: %34
store %32 to %33 : $*String // id: %34
// function_ref default argument 1 of print(_:separator:terminator:)
%35 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA0_ : $@convention(thin) () -> @owned String // user: %36
%36 = apply %35() : $@convention(thin) () -> @owned String // users: %42, %40
// function_ref default argument 2 of print(_:separator:terminator:)
%37 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA1_ : $@convention(thin) () -> @owned String // user: %38
%38 = apply %37() : $@convention(thin) () -> @owned String // users: %41, %40
// function_ref print(_:separator:terminator:)
%39 = function_ref @$ss5print_9separator10terminatoryypd_S2StF : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> () // user: %40
%40 = apply %39(%24, %36, %38) : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()
release_value %38 : $String // id: %41
release_value %36 : $String // id: %42
release_value %24 : $Array<Any> // id: %43
%44 = integer_literal $Builtin.Int32, 0 // user: %45
%45 = struct $Int32 (%44 : $Builtin.Int32) // user: %46
return %45 : $Int32 // id: %46
} // end sil function 'main'
3. 類的加載流程
3.1 純Swift代碼
??在菜單欄:Debug
--> Debug workflow
--> Always show Disassembly
運行源碼,斷點可看到匯編代碼辨宠,由于筆者運行的是Mac上遗锣,所以只要關注call
指令即可(ARM64關注bl
货裹、b
嗤形、br
等指令)』≡玻看到下斷點到call __allocating_init
的指令出,然后按住control + ?箭頭
單步進入可看到如下圖:
再swift_allocObject
流程的時候赋兵,單步進入,里面并沒有看到流程了搔预,此時需要借助Swift源碼進行分析霹期。
??使用VS Code打開 Swift源碼 全局搜索swift_allocObject
,或者直接command+p
輸入HeapObject
定位到文件拯田,找到swift_allocObject
函數(shù)历造,在其上方有一個_swift_allocObject_
函數(shù):
swift_slowAlloc
函數(shù)
3.2繼承自NSObject
4.對象與類的內(nèi)存結構
我們從上面的_swift_allocObject_
中可以看到return
的** object**對象是auto object = reinterpret_cast<HeapObject *>(swift_slowAlloc(requiredSize, requiredAlignmentMask))
這條語句生成的,類型是HeapObject
:
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \ InlineRefCounts refCounts
HeapMetadata
只是一個別名船庇,真正是TargetHeapMetadata
而
TargetHeapMetadata
又繼承自TargetMetadata
吭产,這個里面根據(jù)MetadataKind
創(chuàng)建TargetHeapMetadata
,并且如果需要Swift與Object-C交互(SWIFT_OBJC_INTEROP
默認為1)還根據(jù)TargetAnyClassMetadata
也就是isa
創(chuàng)建鸭轮。TargetMetadata
結構體臣淤,基本到這里,這就是基類了窃爷,但是還是沒有看見其成員變量邑蒋,基本到這就到了瓶頸了。
但是既然kind可以理解為isa按厘,那么基本MetadataKind就是一個重點医吊,在類型的descriptor下,什么情況下是類逮京,然后就發(fā)現(xiàn)了這個函數(shù)getTypeContextDescriptor
,根據(jù)kind來區(qū)分是class還是其他卿堂,如果是類的時候注意到TargetClassMetadata
kind:
1.class
2.Struct
3.Enum
4.Optional
5.ForeignClass
TargetClassMetadata
函數(shù),以及父類TargetAnyClassMetadata
由此基本上類的數(shù)據(jù)結構可以展開為:
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
}
5.驗證--將結構體綁定成為指針類型
// 實例對象的數(shù)據(jù)結構
// UnsafeRawPointer 就是一個原生指針
// 有一個64位的refcounted造虏,可以拆分成兩個32位的
struct HeapObject{
var metadata: UnsafeRawPointer
var refcounted1: UInt32
var refcounted2: UInt32
}
class PSYModel{
var age: Int = 18
var name: String = "psy"
}
var t = PSYModel()
// 獲取實例對象的指針是一個原生指針類型
let objcRawPtr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
// 綁定內(nèi)存
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self
, capacity: 1)
print("end")
下斷點lldb打佑獭:
// Metadata類似于類內(nèi)存結構
struct Metadata{
var kind: Int // 可理解成isa
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 PSYModel{
var age: Int = 18
var name: String = "psy"
}
var t = PSYModel()
// 獲取實例對象的指針是一個原生指針類型
let objcRawPtr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
// 綁定內(nèi)存
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self
, capacity: 1)
let metadata = objcPtr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
MemoryLayout<Metadata>.stride
print("end")
下斷點麦箍,lldb輸出如下: