一洲脂、指針
1.1 為什么說指針不安全
- ?如我們在創(chuàng)建?個對象的時候,是需要在堆分配內(nèi)存空間的剧包。但是這個內(nèi)存空間的生命周期是有限的恐锦,也就意味著如果我們使?指針指向這塊內(nèi)存空間往果,如果當(dāng)前內(nèi)存空間的?命周期已經(jīng)到了(引?計數(shù)為0),那么我們當(dāng)前的指針是不是就變成了未定義的行為了一铅。
- 我們創(chuàng)建的內(nèi)存空間是有邊界的陕贮,?如我們創(chuàng)建?個??為
10
的數(shù)組,這個時候我們通過指針訪問 到了index = 11
的位置馅闽,這個時候數(shù)組是不是就越界了飘蚯,訪問了?個未知的內(nèi)存空間。 - 指針類型與內(nèi)存的值類型不?致福也,也是不安全的。
1.2 指針類型
Swift中的指針分為兩類, typed pointer
指定數(shù)據(jù)類型指針, raw pointer
未指定數(shù)據(jù)類型的指針(原?指針)攀圈”┐眨基本上我們接觸到的指針類型有以下?種:
1.2.1 原生指針的使用
我們?起來看?下如何使? Raw Pointer
來存儲 4 個整型的數(shù)據(jù),這?我們需要選取的是 UnsafeMutableRawPointer
///1赘来、開辟一塊內(nèi)存空間
/// UnsafeMutableRawPointer存儲原生指針
/// allocate:開辟空間
///byteCount: 當(dāng)前總的內(nèi)存大小 4個Int整型现喳,一個整型是8字節(jié),共32字節(jié)
///alignment: 對齊的大小
///2犬辰、調(diào)用storeBytes方法存儲當(dāng)前的整型數(shù)值
///of: 存儲值
///as: 值的類型嗦篱,當(dāng)前類型是整型
///3、我們打印輸出驗證一下
///調(diào)用load方法加載當(dāng)前內(nèi)存當(dāng)中的數(shù)據(jù)
///fromByteOffset: 距離首地址的字節(jié)的大小幌缝,每次移動i * 8字節(jié)
///as: 值的類型灸促,當(dāng)前類型是整型
///4、收回并釋放對應(yīng)的內(nèi)存空間
/// deallocate
let p = UnsafeMutableRawPointer.allocate(byteCount: 4 * 8, alignment: 8)
for i in 0 ..< 4 {
p.storeBytes(of: i, as: Int.self)
}
for i in 0 ..< 4 {
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("index: \(i), value: \(value)")
}
p.deallocate()
編譯運行涵卵,我們調(diào)用打印數(shù)據(jù)查看一下
index: 0, value: 3
index: 1, value: 0
index: 2, value: 16
index: 3, value: 0
結(jié)果發(fā)現(xiàn)浴栽,這個打印結(jié)果并不能和我們存儲的數(shù)據(jù)一致。那么問題出在哪里哪轿偎?
我們在存儲后打印一下p的內(nèi)存地址
省略......
for i in 0 ..< 4 {
p.storeBytes(of: i, as: Int.self)
}
print(p)
lldb打印結(jié)果如下:
0x000000010073c4e0
(lldb) x/8g 0x000000010073c4e0
0x10073c4e0: 0x0000000000000003 0x0000000000000000
0x10073c4f0: 0x0000000000000010 0x0000000000000000
0x10073c500: 0x000000004d55545a 0x000020a000000000
0x10073c510: 0x4d55545a00000000 0x0000000000000000
這?很顯然是我們對當(dāng)前數(shù)據(jù)的存儲?式不對典鸡,按道理來說我們是 8 個字節(jié), 8個字節(jié)的排列開來坏晦,? 在這個過程中存儲的好像不知道每個數(shù)據(jù)與數(shù)據(jù)之間的間距萝玷,所以這?我們需要指定?個東?,那就是每個數(shù)據(jù)之間在內(nèi)存中的間距昆婿。
經(jīng)探究發(fā)現(xiàn)球碉,是因為我們存儲時storeBytes并沒有移動對應(yīng)的步長信息導(dǎo)致的。那么什么是步長信息哪挖诸,我們首先要了解一個枚舉類型MemoryLayout
汁尺。
為了便于理解MemoryLayout
,我們先來看下下面這個例子:
struct ZGTeacher {
var age: Int = 18
}
let size = MemoryLayout<ZGTeacher>.size
let stride = MemoryLayout<ZGTeacher>.stride
let alignment = MemoryLayout<ZGTeacher>.alignment
print(size, stride, alignment)
lldb打印結(jié)果
8 8 8
我們給ZGTeacher結(jié)構(gòu)體新增一個Bool屬性看一下它的變化多律。
struct ZGTeacher {
var age: Int = 18
///新增一個Bool屬性
var sex: Bool = true
}
省略......
lldb打印結(jié)果如下:
9 16 8
其中 alignment
是不變的痴突,但是 size
和 stride
都變了搂蜓。
MemoryLayout
是Swift標(biāo)準(zhǔn)庫中定義的一個枚舉,顧名思義其是用于獲取內(nèi)存相關(guān)信息辽装,MemoryLayout<Int>
則是一種泛型的用法帮碰,調(diào)用其size
屬性可以獲取某種數(shù)據(jù)類型所占內(nèi)存空間的字節(jié)數(shù),是其在內(nèi)存中真實占用大小拾积。調(diào)用其stride
屬性可以獲取某種數(shù)據(jù)類型所開辟內(nèi)存空間的字節(jié)數(shù)殉挽,是系統(tǒng)為其分配的內(nèi)存大小。調(diào)用其alignment
屬性可以獲取某種數(shù)據(jù)類型所需要的對齊信息拓巧,指的是其當(dāng)前內(nèi)存對齊方式斯碌,是1字節(jié)對齊,4字節(jié)對齊等肛度。
這?我們回到我們的指針操作傻唾,此時我們應(yīng)該明?我們在存儲 4 個連續(xù)整型的數(shù)據(jù)時候的問題了,那么 就是我們并沒有指定當(dāng)前 Int 數(shù)據(jù)在排列過程中每個數(shù)據(jù)和每個數(shù)據(jù)之間的間隔是多少承耿?
代碼修改如下:
///advanced 移動對應(yīng)的步長冠骄,來存放內(nèi)容
let size = MemoryLayout<Int>.size
let stride = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let p = UnsafeMutableRawPointer.allocate(byteCount: 4 * stride, alignment: alignment)
for i in 0 ..< 4 {
p.advanced(by: i * stride).storeBytes(of: i, as: Int.self)
///(p + i * stride).storeBytes(of: i, as: Int.self)
}
print(p)
for i in 0 ..< 4 {
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("index: \(i), value: \(value)")
}
p.deallocate()
lldb打印結(jié)果:
0x0000000105058e30
index: 0, value: 0
index: 1, value: 1
index: 2, value: 2
index: 3, value: 3
這才是正確的,符合我們期望的存儲內(nèi)容打印加袋。
其中
p.advanced(by: i * stride).storeBytes(of: i, as: Int.self)
也等價于
///p凛辣,基地址,i *stride职烧,移動對應(yīng)的步長位置來存放i
(p + i * stride).storeBytes(of: i, as: Int.self)
1.2.2 泛型指針的使用
這?的泛型指針相?較原?指針來說扁誓,其實就是指定當(dāng)前指針已經(jīng)綁定到了具體的類型。同樣的阳堕,我們 還是通過?個例?來解釋?下跋理。
var age = 18
withUnsafePointer(to: &age) { ptr in
print(ptr)
}
lldb輸出打印
0x0000000100008058
我們得到了age這個變量的內(nèi)存指針。那么如果我們想要修改age這個變量可以怎么辦哪恬总?
在進(jìn)?泛型指針訪問的過程中前普,我們并不是使? load 和 store ?法來進(jìn)?存儲操作。這?我們使?到當(dāng)前泛型指針內(nèi)置的變量 pointee
壹堰。 獲取 UnsafePointer 的?式有兩種拭卿。
?種?式就是通過已有變量獲取,如下:
var age = 18
withUnsafePointer(to: &age) { ptr in
print(ptr)
}
age = withUnsafePointer(to: age) { ptr in
return ptr.pointee + 20
}
print(age)
發(fā)現(xiàn)更改后贱纠,它的存儲地址和存儲的值已經(jīng)發(fā)生改變峻厚。
0x0000000100008068
38
注意一點,這里我們無法直接修改ptr.pointee
谆焊,如果想要修改惠桃,我們可以通過可變類型的MutablePointer
,代碼如下:
var age = 18
withUnsafePointer(to: &age) { ptr in
print(ptr)
}
withUnsafeMutablePointer(to: &age) { ptr in
ptr.pointee += 50
}
print(age)
lldb輸出打印結(jié)果:
x0000000100008068
68
還有一種方式就是直接分配內(nèi)存
var age = 18
///1、分配一塊Int類型的內(nèi)存空間辜王,注意這個時候當(dāng)前內(nèi)存空間還沒有被初始化
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
///2劈狐、為age initialize初始化分配的內(nèi)存空間
ptr.initialize(to: age)
///3、訪問當(dāng)前內(nèi)存的值呐馆,直接通過pointee屬性來進(jìn)行訪問
print(ptr.pointee)
這?我們看?張圖就?較清晰了:
下面我們操作一個結(jié)構(gòu)體來實踐一下肥缔。
struct ZGTeacher {
var age: Int
var height: Double
}
下面我們來嘗試看一下如何用指針來訪問這個結(jié)構(gòu)體。代碼如下:
struct ZGTeacher {
var age: Int
var height: Double
}
///capacity: 比如我們需要開辟5個內(nèi)存空間的大小
///allocate: 開辟空間
var ptr = UnsafeMutablePointer<ZGTeacher>.allocate(capacity: 5)
///初始化我們的指針內(nèi)存汹来,并存儲對應(yīng)的值
ptr[0] = ZGTeacher(age: 18, height: 20.0)
ptr[1] = ZGTeacher(age: 22, height: 30.0)
///defer關(guān)鍵字续膳,當(dāng)前程序運行完成后會執(zhí)行這塊代碼
defer {
///deinitialize回收5個內(nèi)存空間
ptr.deinitialize(count: 5)
///銷毀對應(yīng)的內(nèi)存空間
ptr.deallocate()
}
我們同樣還可以如下面代碼這樣初始化:
struct ZGTeacher {
var age: Int = 18
var height: Double = 1.85
}
let p = UnsafeMutablePointer<ZGTeacher>.allocate(capacity: 2)
p.initialize(to: ZGTeacher())
p.advanced(by: MemoryLayout<ZGTeacher>.stride).initialize(to: ZGTeacher(age: 20, height: 1.75))
defer {
p.deinitialize(count: 2)
p.deallocate()
}
1.2.3 內(nèi)存指針的使用
接下來,我們來嘗試用指針讀取Mach-o文件中屬性的名稱收班,代碼如下:
class ZGTeacher {
var age: Int = 18
var name: String = "Zhang"
}
var size: UInt = 0
///__swift5_types section 的pFile
var ptr = getsectdata("__TEXT", "__swift5_types", &size)
///pFile地址 0x0000000100007e24
print("ptr: \(ptr!)")
///獲取程序當(dāng)前運行基地址 0x0000000100000000
///mach_header
var excute_header: UnsafePointer<mach_header>?
let count = _dyld_image_count()
for i in 0..<count {
let ptr = _dyld_get_image_header(i)
if ptr!.pointee.filetype == MH_EXECUTE {
excute_header = ptr
}
}
var mhHeaderPtr = excute_header
print("程序當(dāng)前運行基地址mhHeaderPtr: \(mhHeaderPtr!)")
///Segment Name
var setCommond64Ptr = getsegbyname("__LINKEDIT")
///真實基地址
var linkBaseAddress: UInt64 = 0
///vmaddr虛擬基地址
///fileoff當(dāng)前文件的偏移量
///真實基地址 = 虛擬基地址 - 文件的偏移量
///linkBaseAddress 十進(jìn)制4294967296 十六進(jìn)制0x100000000
///十進(jìn)制轉(zhuǎn)十六進(jìn)制,返回的是字符串格式String(X,radix:16)
if let vmaddr = setCommond64Ptr?.pointee.vmaddr, let fileoff = setCommond64Ptr?.pointee.fileoff {
linkBaseAddress = vmaddr - fileoff
// print("真實基地址linkBaseAddress: \(linkBaseAddress)")
print("真實基地址linkBaseAddress: " + "0x" + String(linkBaseAddress, radix: 16))
}
///offset = 當(dāng)前的pFile地址 - 真實基地址
///offset: 十進(jìn)制32292 十六進(jìn)制0x7e24
var offset: UInt64 = 0
if let unwrappedPtr = ptr {
///把當(dāng)前的地址信息轉(zhuǎn)換為UInt64類型
let intRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: unwrappedPtr)))
offset = intRepresentation - linkBaseAddress
// print("偏移量: \(offset)")
print("偏移量: " + "0x" + String(offset, radix: 16))
}
///將首地址ptr轉(zhuǎn)換為UInt64類型的坟岔,方便下面的計算
let mhHeaderPtr_IntRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: mhHeaderPtr)))
///存放內(nèi)容的真實地址
var dataLoAddress = mhHeaderPtr_IntRepresentation + offset
/////將存放內(nèi)容的真實地址轉(zhuǎn)換為指針類型
//var dataLoAddressPtr = withUnsafePointer(to: &dataLoAddress) {print($0)}
///真實pfile存儲的value(內(nèi)容)地址
var dataLoContent = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: dataLoAddress) ?? 0)?.pointee
///十進(jìn)制4294966164,十六進(jìn)制轉(zhuǎn)換一下0xfffffb94
//print("真實pfile存儲的value(內(nèi)容)地址 dataLoContent: \(dataLoContent!)")
print("真實pfile存儲的value(內(nèi)容)地址 dataLoContent: " + "0x" + String(dataLoContent!, radix: 16))
///描述文件pfile在Mach-O文件中的偏移信息
///當(dāng)前pfile真實地址偏移量 = 當(dāng)前pfile真實地址 + 偏移量 - 真實基地址
let typeDescOffset = UInt64(dataLoContent!) + offset - linkBaseAddress
///當(dāng)前pfile真實地址 = UInt64類型的當(dāng)前pfile真實地址偏移量 + UInt64類型的真實基地址
var typeDescAdress = typeDescOffset + mhHeaderPtr_IntRepresentation
///十進(jìn)制4295014840闺阱,十六進(jìn)制轉(zhuǎn)換一下0x10000b9b8
//print("當(dāng)前pfile真實地址 typeDescAdress: \(typeDescAdress)")
print("當(dāng)前pfile真實地址 typeDescAdress: " + "0x" + String(typeDescAdress, radix: 16))
///為了打印typeDescAdress內(nèi)部結(jié)構(gòu)
struct TargetClassDescriptor {
var flags: UInt32
var parent: UInt32
var name: Int32
var accessFunctionPointer: Int32
var fieldDescriptor: Int32
var superClassType: Int32
var metadataNegativeSizeInWords: UInt32
var metadataPositiveSizeInWords: UInt32
var numImmediateMembers: UInt32
var numFields: UInt32
var fieldOffsetVectorOffset: UInt32
var Offset: UInt32
var size: UInt32
}
///將當(dāng)前pfile真實地址轉(zhuǎn)換為指向TargetClassDescriptor的指針類型炮车,并獲取它的pointee屬性
let classDescriptor = UnsafePointer<TargetClassDescriptor>.init(bitPattern: Int(exactly: typeDescAdress) ?? 0)?.pointee
if let name = classDescriptor?.name {
let nameOffset = Int64(name) + Int64(typeDescOffset) + 8
// print("類名偏移量 nameOffset: \(nameOffset)")
print("類名偏移量 nameOffset: " + "0x" + String(nameOffset, radix: 16))
let nameAddress = nameOffset + Int64(mhHeaderPtr_IntRepresentation)
// print("類名地址 nameAddress: \(nameAddress)")
print("類名地址 nameAddress: " + "0x" + String(nameAddress, radix: 16))
if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(nameAddress)) {
///類名
print("類名:" + String(cString: cChar))
}
}
/// 獲取屬性
/// 獲取屬性相關(guān)的filedDescriptor 在運行中的內(nèi)存地址
let filedDescriptorRelaticveAddress = typeDescOffset + 4 * 4 + mhHeaderPtr_IntRepresentation
///十進(jìn)制4295014856,十六進(jìn)制轉(zhuǎn)換一下0x10000b9c8
//print("filedDescriptor的地址 filedDescriptorRelaticveAddress: \(filedDescriptorRelaticveAddress)")
print("filedDescriptor的地址 filedDescriptorRelaticveAddress: " + "0x" + String(filedDescriptorRelaticveAddress, radix: 16))
struct FieldDescriptor {
var mangledTypeName: Int32
var superclass: Int32
var Kind: UInt16
var fieldRecordSize: UInt16
var numFields: UInt32
// var fieldRecords: [FieldRecord]
}
struct FieldRecord {
var Flags: UInt32
var mangledTypeName: Int32
var fieldName: UInt32
}
///獲取fieldDescriptor 偏移量offset
let fieldDescriptorOffset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: filedDescriptorRelaticveAddress) ?? 0)?.pointee
///fieldDescriptor的Offset偏移量的地址 十進(jìn)制656酣溃,十六進(jìn)制轉(zhuǎn)換一下0x290
//print("fieldDescriptor的Offset偏移量的地址 fieldDescriptorOffset: \(fieldDescriptorOffset!)")
print("fieldDescriptor的Offset偏移量的地址 fieldDescriptorOffset: " + "0x" + String(fieldDescriptorOffset!, radix: 16))
///獲取 FieldDescriptor 的在運行中的內(nèi)存地址
let fieldDescriptorAddress = filedDescriptorRelaticveAddress + UInt64(fieldDescriptorOffset!)
///將 FieldDescriptor 的內(nèi)存地址直接轉(zhuǎn)換成指向 FieldDescriptor 結(jié)構(gòu)體的指針
let fieldDescriptor = UnsafePointer<FieldDescriptor>.init(bitPattern: Int(exactly: fieldDescriptorAddress) ?? 0)?.pointee
///循環(huán)遍歷屬性
for i in 0 ..< fieldDescriptor!.numFields {
///FieldRecord 結(jié)構(gòu)體由 3個 4字節(jié)組成,并且保持3 * 4 = 12字節(jié)對齊
let stride: UInt64 = UInt64(i * 3 * 4)
let fieldRecordAddress = fieldDescriptorAddress + stride + 16
// print(fieldRecordRelactiveAddress)
// let fieldRecord = UnsafePointer<FieldRecord>.init(bitPattern: Int(exactly: fieldRecordAddress) ?? 0)?.pointee
// print(fieldRecord)
let fieldNameRelactiveAddress = UInt64(2 * 4) + fieldRecordAddress - linkBaseAddress + mhHeaderPtr_IntRepresentation
let offset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: fieldNameRelactiveAddress) ?? 0)?.pointee
// print(offset)
let fieldNameAddress = fieldNameRelactiveAddress + UInt64(offset!) - linkBaseAddress
if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(fieldNameAddress)){
print(String(cString: cChar))
}
}
// 獲取v-table
// 函數(shù)的結(jié)構(gòu)體
struct TargetMethodDescriptor {
var kind: UInt32
var offset: UInt32
}
// 獲取方法的數(shù)量
if let methods = classDescriptor?.methods {
for i in 0..<methods {
// 獲取v-table的的首地址
let VTableRelaticveAddress = typeDescOffset + 4 * 13 + mhHeaderPtr_IntRepresentation
// 獲取當(dāng)前函數(shù)的地址
let currentMethodAddress = VTableRelaticveAddress + UInt64(i) * UInt64(MemoryLayout<TargetMethodDescriptor>.size)
// 將 當(dāng)前函數(shù) 的內(nèi)存地址直接轉(zhuǎn)換成指向 TargetMethodDescriptor 結(jié)構(gòu)體的指針
let currentMethod = UnsafePointer<TargetMethodDescriptor>.init(bitPattern: Int(exactly: currentMethodAddress) ?? 0)?.pointee
// 獲取到imp的地址
let impAddress = currentMethodAddress + 4 + UInt64(currentMethod!.offset) - linkBaseAddress
print(impAddress);
}
}
1.3 內(nèi)存綁定
swift提供了三種不同的API來綁定/重新綁定指針:
- assumingMemoryBound(to:)
func testPointer(_ p: UnsafePointer<Int>) {
print(p)
}
let tuple = (10, 20)
withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer<(Int, Int)>) in
testPointer(tuplePtr)
}
編譯運行纪隙,發(fā)現(xiàn)報錯了赊豌,如下圖所示
那么想要順利訪問到元組的值我們可以怎么辦哪? 首先,第一點绵咱,我們先把我們的指針轉(zhuǎn)換為原生指針
UnsafeRawPointer
,然后我們調(diào)用assumingMemoryBound
綁定成對應(yīng)類型。
func testPointer(_ p: UnsafePointer<Int>) {
print(p[0], p[1])
}
let tuple = (10, 20)
withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer<(Int, Int)>) in
testPointer(UnsafeRawPointer(tuplePtr).assumingMemoryBound(to: Int.self))
}
編譯一下,正確輸出打印元組的值 10
容客,20
那么我們使用assumingMemoryBound的意義是什么翰意?
有時候我們的類型只有這種原生指針UnsafeRawPointer,或者說像UnsafePointer<(Int, Int)>和UnsafePointer<Int>這種麸锉,兩種值類型相似钠绍,但是我們又不想經(jīng)過一系列的轉(zhuǎn)換操作來增加代碼復(fù)雜度,對我們的指針進(jìn)行生硬的轉(zhuǎn)換花沉,那么這個時候我們就可以使用這個API assumingMemoryBound
來告訴我們的編譯器自己預(yù)期的類型柳爽,不需要編譯器再重復(fù)檢查(注意:這?只是讓編譯器繞過類型檢查,并沒有發(fā)?實際類型的轉(zhuǎn)換
)碱屁。
-
bindMemory(to: , capacity: )
調(diào)用bindMemory
綁定成對應(yīng)類型磷脯,這里我們發(fā)生了實際類型的轉(zhuǎn)換。如果當(dāng)前的內(nèi)存沒有綁定類型娩脾,那么我們就首次綁定類型赵誓,如果有它當(dāng)前的原有類型,那么調(diào)用這個API,我們重新綁定為指定類型俩功。
func testPointer(_ p: UnsafePointer<Int>) {
print(p[0], p[1])
}
let tuple = (10, 20)
withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer<(Int, Int)>) in
testPointer(UnsafeRawPointer(tuplePtr).bindMemory(to: Int.self, capacity: 1))
}
這里我們將UnsafePointer<(Int, Int)>轉(zhuǎn)換成了UnsafePointer<Int>類型幻枉。
-
withMemoryRebound(to: , capacity: )
withMemoryRebound
就是用來臨時更改我們的類型,減少代碼復(fù)雜度绑雄。
func testPoint(_ p: UnsafePointer<Int8>) {
}
let UInt8Ptr = UnsafePointer<UInt8>.init(bitPattern: 10)
UInt8Ptr?.withMemoryRebound(to: Int8.self, capacity: 1, { (int8Ptr: UnsafePointer<Int8>) in
testPoint(int8Ptr)
})
二展辞、內(nèi)存管理
swift中使??動引?計數(shù)(ARC)機(jī)制
來追蹤和管理內(nèi)存。
class ZGTeacher {
var age: Int = 18
var name: String = "Zhang"
}
var t = ZGTeacher()
///固定寫法万牺,打印這個t實例的內(nèi)存指針
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())
print("end")
打印輸出
0x0000000101252a90
我們用x/8g指令輸出一下
(lldb) x/8g 0x0000000101252a90
0x101252a90: 0x0000000100008198 0x0000000000000003
0x101252aa0: 0x0000000000000012 0x000000676e61685a
0x101252ab0: 0xe500000000000000 0x0000000000000000
0x101252ac0: 0x00000009a0080001 0x00007ff84be59aa0
我們拿到了這個實例對象t的內(nèi)存指針地址罗珍,我們知道實例對象內(nèi)存地址其中的前16
個字節(jié)的后8
個字節(jié)在這個過程當(dāng)中本質(zhì)上是存儲我們的refCounts
,但我們不知道它存儲的到底代表什么意思脚粟,現(xiàn)在我們在Swift源碼里面看一下覆旱。
首先我們先找到refCount
的定義,這里我們在HeapObject.h
文件中搜索
// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
/// 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 *__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)
{ }
我們看到refCounts
是由InlineRefCounts
定義的核无,接下來扣唱,我們沿著這個InlineRefCounts
定義點擊進(jìn)去。
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
可以看到InlineRefCounts
其實是一個模版類团南,接受一個泛型參數(shù)InlineRefCountBits
噪沙。
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
// Out-of-line slow paths.
SWIFT_NOINLINE
void incrementSlow(RefCountBits oldbits, uint32_t inc) SWIFT_CC(PreserveMost);
SWIFT_NOINLINE
void incrementNonAtomicSlow(RefCountBits oldbits, uint32_t inc);
SWIFT_NOINLINE
bool tryIncrementSlow(RefCountBits oldbits);
......
}
可以看到,本質(zhì)上它在操作我們的API的時候都操作的是我們的泛型參數(shù)RefCountBits
吐根。InlineRefCountBits
它其實是一個模版類正歼,RefCounts
本質(zhì)上是對我們當(dāng)前引用計數(shù)的一個包裝,我們引用計數(shù)的具體類型取決于傳進(jìn)來的參數(shù)RefCountBits
拷橘。
// Basic encoding of refcount and flag data into the object's header.
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
friend class RefCountBitsT<RefCountIsInline>;
friend class RefCountBitsT<RefCountNotInline>;
static const RefCountInlinedness Inlinedness = refcountIsInline;
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::SignedType
SignedBitsType;
typedef RefCountBitOffsets<sizeof(BitsType)>
Offsets;
BitsType bits;
點擊Type看一下類型局义,它是一個uint64_t類型的位域信息
template <>
struct RefCountBitsInt<RefCountNotInline, 4> {
typedef uint64_t Type;
typedef int64_t SignedType;
};
看到這里我們可以得出結(jié)論,我們的引用計數(shù)它是一個64位的位域信息冗疮。在這個位域信息里存儲了和我們當(dāng)前這個運行生命周期相關(guān)的引用計數(shù)萄唇。
當(dāng)我們創(chuàng)建一個實例對象的時候,當(dāng)前的引用計數(shù)是多少术幔?
我們先從源代碼中找一下它的初始化方法new (object) HeapObject(metadata)
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;
}
點擊HeapObject方法另萤,我們看到了它的初始化賦值方法refCounts(InlineRefCounts::Initialized)
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
點擊或者搜索Initialized
,可以看到以下代碼:
public:
enum Initialized_t { Initialized };
enum Immortal_t { Immortal };
// RefCounts must be trivially constructible to avoid ObjC++
// destruction overhead at runtime. Use RefCounts(Initialized)
// to produce an initialized instance.
RefCounts() = default;
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
// Refcount of an immortal object has top and bottom bits set
constexpr RefCounts(Immortal_t)
: refCounts(RefCountBits(RefCountBits::Immortal)) {}
RefCountBits
就是我們剛才講的模版類特愿。想要了解這個類那么我們就去搜索一下RefCountBitsT
SWIFT_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
strongExtraCount 傳入的0仲墨,StrongExtraRefCountShift 在33位,unownedCount 傳入的是1揍障,UnownedRefCountShift 1位,
0左移33位是0目养,1左移1位是2。
接下來我們來看一下當(dāng)我們做一個強(qiáng)引用的時候它的引用計數(shù)是怎么變化的哪毒嫡?
class ZGTeacher {
var age: Int = 18
var name: String = "Zhang"
}
var t = ZGTeacher()
///固定寫法癌蚁,打印這個t實例的內(nèi)存指針
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())
var t1 = t
var t2 = t
print("end")
我們逐步放開斷點幻梯,并用x/8g指令打印輸出,看到它的強(qiáng)引用計數(shù)變?yōu)榱?努释。通過位移運算左移33
位碘梢,高34
位,每次加2
伐蒂。
我們來看一下位域布局圖
- 1 ~31位存儲的是無主引用
- 32位存儲的是當(dāng)前的類是否正在析構(gòu)
- 33 ~ 62位存儲的是強(qiáng)引用
我們簡單用代碼來驗證一下第32位信息
class ZGTeacher {
var age: Int = 18
var name: String = "Zhang"
}
var t: ZGTeacher? = ZGTeacher()
///固定寫法煞躬,打印這個t實例的內(nèi)存指針
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())
t = nil
print("end")
32位為1,表示正在析構(gòu)逸邦。
那么引用計數(shù)是如何操作強(qiáng)引用的哪恩沛,我們也可以到源碼里看一下。
SWIFT_ALWAYS_INLINE
static HeapObject *_swift_retain_(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
if (isValidPointerForNativeRetain(object))
object->refCounts.increment(1);
return object;
}
SWIFT_ALWAYS_INLINE
void increment(uint32_t inc = 1) {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// constant propagation will remove this in swift_retain, it should only
// be present in swift_retain_n
if (inc != 1 && oldbits.isImmortal(true)) {
return;
}
RefCountBits newbits;
do {
newbits = oldbits;
bool fast = newbits.incrementStrongExtraRefCount(inc);
if (SWIFT_UNLIKELY(!fast)) {
if (oldbits.isImmortal(false))
return;
return incrementSlow(oldbits, inc);
}
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
}
SWIFT_NODISCARD SWIFT_ALWAYS_INLINE bool
incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
這里也是通過位域運算得來的缕减。使用強(qiáng)引用就會造成一個問題:循環(huán)引用
雷客。我們來看一下以下案例:
class ZGTeacher {
var age: Int = 18
var name: String = "zhang"
var subject: ZGTeacher?
}
class ZGSubject {
var subjectName: String
var subjectTeacher: ZGTeacher
init(_ subjectName: String, _ subjectTeacher: ZGTeacher) {
self.subjectName = subjectName
self.subjectTeacher = subjectTeacher
}
}
var t = ZGTeacher()
var subject = ZGSubject.init("swift", t)
t.subject = subject
這里實例對象相互持有,造成對象無法釋放桥狡,產(chǎn)生了循環(huán)引用搅裙。那么我們怎么解決這一問題哪?在swit中有兩種方式裹芝,第一種是我們的弱引用部逮,第二種方式是無主引用Unowned。
2.1 弱引用
弱引?不會對其引?的實例保持強(qiáng)引?嫂易,因?不會阻? ARC 釋放被引?的實例甥啄。這個特性阻?了引?變?yōu)檠h(huán)強(qiáng)引?。聲明屬性或者變量時炬搭,在前?加上 weak
關(guān)鍵字表明這是?個弱引?。 由于弱引?不會強(qiáng)保持對實例的引?穆桂,所以說實例被釋放了弱引?仍舊引?著這個實例也是有可能的宫盔。 因此,ARC 會在被引?的實例被釋放時?動地設(shè)置弱引?為 nil 享完。由于弱引?需要允許它們的值為 nil 灼芭, 它們?定得是可選類型。
class ZGTeacher {
var age: Int = 18
var name: String = "zhang"
}
weak var t = ZGTeacher()
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())
print("end")
給weak var t = ZGTeacher()加上斷點般又,并打開Xcode的匯編調(diào)試彼绷,編譯運行一下
發(fā)現(xiàn)它明顯執(zhí)行了一個
swift_weakInit
。我們到swift源碼里來搜索一下這個方法茴迁。
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
聲明一個weak
變量相當(dāng)于定義了一個WeakReference
對象寄悯。
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// Preflight failures before allocating a new side table.
if (oldbits.hasSideTable()) {
// Already have a side table. Return it.
return oldbits.getSideTable();
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
// Preflight passed. Allocate a side table.
// FIXME: custom side table allocator
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
auto newbits = InlineRefCountBits(side);
do {
if (oldbits.hasSideTable()) {
// Already have a side table. Return it and delete ours.
// Read before delete to streamline barriers.
auto result = oldbits.getSideTable();
delete side;
return result;
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
side->initRefCounts(oldbits);
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
HeapObject {
isa
InlineRefCounts {
atomic<InlineRefCountBits> {
strong RC + unowned RC + flags
OR
HeapObjectSideTableEntry*
}
}
}
HeapObjectSideTableEntry {
SideTableRefCounts {
object pointer
atomic<SideTableRefCountBits> {
strong RC + unowned RC + weak RC + flags
}
}
}
在我們的swift里面,本質(zhì)上存在著兩種引用計數(shù)堕义,一種是InlineRefCounts
猜旬,里面就是strong RC + unowned RC + flags
,如果我們當(dāng)前是有我們的引用計數(shù),此時它就存儲了HeapObjectSideTableEntry
洒擦,里面包含了strong RC + unowned RC + weak RC + flags
椿争。
可以看到這里的本質(zhì)是用64
位的指針,把當(dāng)前的side
存儲到64
位位域中熟嫩,并設(shè)置一些標(biāo)記位秦踪。
Side Table
是?種類名為 HeapObjectSideTableEntry
的結(jié)構(gòu),??也有 RefCounts
成員掸茅,內(nèi)部是 SideTableRefCountBits
椅邓,其實就是原來的 uint64_t
加上?個存儲弱引?數(shù)的 uint32_t
。
2.2 Unowned
和弱引?類似倦蚪,?主引?不會牢牢保持住引?的實例希坚。但是不像弱引?,總之陵且,?主引?假定是永遠(yuǎn)有值裁僧。
class ZGTeacher {
var age: Int = 18
var name: String = "zhang"
}
var t: ZGTeacher?
t = ZGTeacher()
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())
unowned var t1 = t
t = nil
print(t1)
這里產(chǎn)生了崩潰,因為unowned修飾的t1必須是假定有值的慕购,t或者t1為nil就產(chǎn)生了崩潰聊疲。
當(dāng)我們知道兩個對象的?命周期并不相關(guān),那么我們必須使? weak
沪悲。相反获洲,?強(qiáng)引?對象擁有和強(qiáng)引?對象同樣或者更?的?命周期的話,則應(yīng)該使? unowned
殿如。
我們來看一下這個例子:
class ZGTeacher {
var age: Int = 18
var name: String = "zhang"
var subject: ZGSubject?
}
class ZGSubject {
var subjectName: String
var subjectTeacher: ZGTeacher
init(_ subjectName: String, _ subjectTeacher: ZGTeacher) {
self.subjectName = subjectName
self.subjectTeacher = subjectTeacher
}
}
var t = ZGTeacher()
var subject = ZGSubject.init("swift", t)
t.subject = subject
這個過程中我們的Teacher和SubjectName贡珊,如果老師不在了,那么這個過程中所講授的課程也就不在了涉馁。
這個過程中我們可以通過unowned來解決這個循環(huán)引用门岔,也就意味著當(dāng)前的生命周期ZGTeacher更長。
class ZGTeacher {
var age: Int = 18
var name: String = "zhang"
var subject: ZGSubject?
}
class ZGSubject {
var subjectName: String
///這里添加 unowned
unowned var subjectTeacher: ZGTeacher
init(_ subjectName: String, _ subjectTeacher: ZGTeacher) {
self.subjectName = subjectName
self.subjectTeacher = subjectTeacher
}
}
var t = ZGTeacher()
var subject = ZGSubject.init("swift", t)
t.subject = subject
2.3 Weak VS Unowned比較
注意weak
相對于unowned
更安全烤送,但是unowned
相對于weak
性能更好寒随。因為weak
需要重新創(chuàng)建一個sideTable
散列表,還要對我們當(dāng)前的sideTable
散列表進(jìn)行操作帮坚,而unowned
直接操作了我們64
的信息妻往,但是在使用過程中要確保unowned
是有值的。
2.4 閉包循環(huán)引用
var age = 18
let closure = {
age += 1
}
closure()
print(age)
lldb輸出打印結(jié)果
19
我們的閉包會?般默認(rèn)捕獲我們外部的變量试和,閉包內(nèi)部對變量的修改將會改變外部原始變量的值
讯泣。
下面的案例就是閉包循環(huán)引用的經(jīng)典案例,對象無法釋放灰署。
class ZGTeacher {
var age: Int = 18
var name: String = "zhang"
var testClosure: (() -> ())?
deinit {
print("ZGTeacher deinit")
}
}
func test() {
let t = ZGTeacher()
t.testClosure = {
t.age += 1
}
print("end")
}
test()
那么我們?nèi)绾谓鉀Q這一問題哪判帮?可以使用我們當(dāng)前的捕獲列表局嘁。
class ZGTeacher {
var age: Int = 18
var name: String = "zhang"
var testClosure: (() -> ())?
deinit {
print("ZGTeacher deinit")
}
}
func test() {
let t = ZGTeacher()
t.testClosure = { [weak t] in
t!.age += 1
}
print("end")
}
test()