Swift進階-類與結(jié)構(gòu)體
Swift-函數(shù)派發(fā)
Swift進階-屬性
Swift進階-指針
Swift進階-內(nèi)存管理
Swift進階-TargetClassMetadata和TargetStructMetadata數(shù)據(jù)結(jié)構(gòu)源碼分析
Swift進階-Mirror解析
Swift進階-閉包
Swift進階-協(xié)議
Swift進階-泛型
Swift進階-String源碼解析
Swift進階-Array源碼解析
前言
為什么說指針不安全?
- ?如我們在創(chuàng)建?個對象的時候,是需要在堆分配內(nèi)存空間的。但是這個內(nèi)存空間的聲明周期是有限的,也就意味著如果我們使?指針指向這塊內(nèi)容空間,如果當前內(nèi)存空間的?命周期啊到了(引?計數(shù)為0),那么我們當前的指針是不是就變成了未定義的?為了(野指針)。
- 我們創(chuàng)建的內(nèi)存空間是有邊界的,?如我們創(chuàng)建?個??為10的數(shù)組,這個時候我們通過指針訪問 到了 index = 11 的位置,這個時候是不是就越界了,訪問了?個未知的內(nèi)存空間。
- 指針類型與內(nèi)存的值類型不?致,也是不安全的。(Int * 和 Int8 *類型容易造成精度缺失)
1.指針類別
Swift中的指針分為兩類:
typed pointer: 指定數(shù)據(jù)類型指針,即UnsafePointer<T>,其中T表示泛型
raw pointer: 未指定數(shù)據(jù)類型的指針(原生指針) ,即UnsafeRawPointer
與OC中的指針的對比??
| OC | swift | 解釋 |
|---|---|---|
| const T * | unsafePointer<T> | 指針及所指向的內(nèi)容都不可變
|
| T * | unsafeMutablePointer | 指針及所指向的內(nèi)容都可變
|
| const void * | unsafeRawPointer | 無類型指針,指向的值必須是常量(指向的內(nèi)存區(qū)域未定) |
| void * | unsafeMutableRawPointer | 無類型指針,指向的內(nèi)存區(qū)域未定,也叫通用指針(指向的內(nèi)存區(qū)域未定) |
首先我們了解一下內(nèi)存的字節(jié)對齊、實際大小、步長:
struct Teacher {
var age: Int = 18
var sex: Bool = true
}
print(MemoryLayout<Int>.alignment) // 8 字節(jié)對齊
print(MemoryLayout<Teacher>.size) // 9 實際大小
print(MemoryLayout<Teacher>.stride) // 16 步長
字節(jié)對齊
alignment: cpu的一個64位尋址周期讀出來的是8字節(jié)。實際大小
size: 一個Teacher()占用內(nèi)存的真實大小 size = Int + Bool / 9 = 8 + 1。步長
stride: 比如說我要存儲連續(xù)的Teacher實例,從當前Teacher()的起始位置到下一個Teacher()的起始位置,就是一個Teacher實例的步長。這里輸出16是因為存儲一個Teacher()需要的內(nèi)存實際大小是9,又因為內(nèi)存的字節(jié)對齊是8,所以要跨兩個8字節(jié)對齊才足夠存儲一個Teacher實例。
1.1 原生指針 raw pointer
使用原生指針需要注意2點??:
a.對于原生指針的內(nèi)存管理是需要手動管理
b.原生指針在使用完需要手動釋放
// 定義一個未知類型的指針:本質(zhì)是分配32字節(jié)大小的空間,指定對齊方式是8字節(jié)對齊
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
for i in 0...3 {
p.storeBytes(of: i, as: Int.self) //存值
}
for i in 0...3 {
//p是當前內(nèi)存的首地址,通過內(nèi)存平移來獲取值
let value = p.load(fromByteOffset: i * 8, as: Int.self) //取值
print("index: \(i), value: \(value)")
}
p.deallocate() //使用完需要手動釋放
預想值是打印的value是0~3,但實際情況卻是:
index: 0, value: 3
index: 1, value: 1152921504606846976
index: 2, value: 8319395793567416323
index: 3, value: 246290604621824
原因是沒有在指定的位置去設值,導致每次都對第一個內(nèi)存空間設值。
解決:存儲時,通過advanced(by:)指定的步長。
// 定義一個未知類型的指針:本質(zhì)是分配32字節(jié)大小的空間,指定對齊方式是8字節(jié)對齊
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
for i in 0...3 {
p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self) //移動對應的步長,存值
}
for i in 0...3 {
//p是當前內(nèi)存的首地址,通過內(nèi)存平移來獲取值
let value = p.load(fromByteOffset: i * 8, as: Int.self) //取值
print("index: \(i), value: \(value)")
}
p.deallocate() //使用完需要手動釋放
/* print
index: 0, value: 0
index: 1, value: 1
index: 2, value: 2
index: 3, value: 3
*/
1.2 類型指針 typed pointer
1.2.1 通過 withUnsafePointer(to:) 方法獲取地址
class Teacher {
var name: String
init(name: String) {
self.name = name
}
}
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
var t = Teacher(name: "安老師")
withUnsafePointer(to: &t) { print($0) }
// 和上面是一樣的,只是方式不同
withUnsafePointer(to: &t) { point in
print(point)
}
}
}
輸出的是變量t的棧內(nèi)存地址(這與Teacher(name: "安老師")在堆內(nèi)存的地址不一樣)
如果我想訪問指針的具體內(nèi)容 pointee:
// 得到的是Teacher實例
withUnsafePointer(to: &t) { print($0.pointee) }
// 訪問Teacher實例的name屬性 - 安老師
withUnsafePointer(to: &t) { print($0.pointee.name) }
如果我想改變屬性的值:
var t = Teacher(name: "安老師")
withUnsafePointer(to: &t) {
return $0.pointee.name = "林老師" // 把return省略也可以
}
print(t.name)
withUnsafePointer(to:) 與 withUnsafeMutablePointer(to:)對于值類型不同:
var age = 20
/* 報錯!不予以修改值
withUnsafePointer(to: &age) { point in
point.pointee += 1
}
*/
withUnsafeMutablePointer(to: &age) { point in
point.pointee += 1
}
1.2.2 類型指針使用API:

通過allocate創(chuàng)建UnsafeMutablePointer,需要注意以下幾點??:
-
initialize與deinitialize需成對使用 -
deinitialize中的count與申請時的capacity需要一致 - 使用完后必須
deallocate
class Teacher {
var name: String
init(name: String) {
self.name = name
}
}
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let tPoint = UnsafeMutablePointer<Teacher>.allocate(capacity: 3)
defer {
tPoint.deinitialize(count: 3)
tPoint.deallocate()
}
tPoint.advanced(by: 0).initialize(to: Teacher(name: "黃老師"))
tPoint.advanced(by: 1).initialize(to: Teacher(name: "劉老師"))
tPoint.advanced(by: 2).initialize(to: Teacher(name: "潘老師"))
// 三種方式訪問
print(tPoint.pointee.name) // 黃老師 首地址指向就是第一個Teacher實例
print(tPoint[1].name) // 劉老師
print(tPoint.successor().pointee.name) // 劉老師 同上一行是一樣效果的
print(tPoint.successor().successor().pointee.name) // 潘老師
}
}
有三種方式訪問:
- 下標
- 內(nèi)存平移
- successor()
/** 三種方式是效果是一樣的 */
let tPoint = UnsafeMutablePointer<Teacher>.allocate(capacity: 3)
defer {
tPoint.deinitialize(count: 3)
tPoint.deallocate()
}
// 內(nèi)存平移
(tPoint + 1).initialize(to: Teacher(name: "劉老師"))
// successor()
tPoint.successor().initialize(to: Teacher(name: "劉老師"))
// advanced(by: 1)
tPoint.advanced(by: 1).initialize(to: Teacher(name: "劉老師"))
為什么tPoint.advanced(by: 1)可以?為什么不用 tPoint.advanced(by: MemoryLayout<Teacher>.stride)?
注意:
關(guān)鍵在于這句代碼 --> let tPoint = UnsafeMutablePointer<Teacher>.allocate(capacity: 3),此時我們是知道tPoint的具體類型的,就是指向Teacher的指針。
在確定指針的類型后,通過步長的移動+1,就表示移動了那個類的實例大小空間+1。
2.內(nèi)存綁定
swift 提供了三種不同的 API 來綁定/重新綁定指針:
- assumingMemoryBound(to:)
- bindMemory(to: capacity:)
- withMemoryRebound(to: capacity: body:)
2.1 assumingMemoryBound(to:) (只是讓編譯器繞過類型檢查,并沒有發(fā)?實際類型的轉(zhuǎn)換)
有些時候我們處理代碼的過程中,只有原始指針(沒有保留指針類型),但此刻對于處理代碼的我們來 說明確知道指針的類型,我們就可以使? assumingMemoryBound(to:) 來告訴編譯器預期的類型。
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
var tuple = (10, 20)
withUnsafePointer(to: &tuple) { tuplePtr in
testPointer(tuplePtr)
}
}
}
func testPointer(_ p: UnsafePointer<Int>) {
print(p[0])
print(p[1])
}
此時在調(diào)用 testPointer會報編譯器錯誤 Cannot convert value of type 'UnsafePointer<(Int, Int)>' to expected argument type 'UnsafePointer<Int>'
而代碼修改一下使用 assumingMemoryBound(to:):
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
var tuple = (10, 20)
withUnsafePointer(to: &tuple) { tuplePtr in
testPointer(UnsafeRawPointer(tuplePtr).assumingMemoryBound(to: Int.self))
}
}
}
func testPointer(_ p: UnsafePointer<Int>) {
print(p[0])
print(p[1])
}
元組時值類型,在本質(zhì)上這塊內(nèi)存空間是連續(xù)的,存放Int類型數(shù)據(jù)。先把元組轉(zhuǎn)化成指針指向元組的首地址tuple[0],通過assumingMemoryBound(to:)告訴編譯器,當前元組的內(nèi)存已經(jīng)綁定過Int了,這時就能騙過編譯器檢查。
2.2 bindMemory(to: capacity:)
?于更改內(nèi)存綁定的類型,如果當前內(nèi)存還沒有類型綁定,則將?次綁定為該類型;否則重新綁定該類型,并且內(nèi)存中所有的值都會變成該類型。
案例:將一個class實例綁定成class的底層數(shù)據(jù)結(jié)構(gòu)
- 定義一個Teacher類
class Teacher {
var name: String
init(name: String) {
self.name = name
}
}
- 從swift源碼得出class的底層結(jié)構(gòu)是
HeapObject,所以我們可以自定義一個HeapObject
struct HeapObject {
var metadata: UnsafeRawPointer // 定義一個未知類型的指針
var refCounts: UInt32 // 引用計數(shù)
}
- 從swift源碼得出
metadata其實也是一個結(jié)構(gòu)體,所以我自定義一個Metadata
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
}
將teacher綁定到結(jié)構(gòu)體內(nèi)存中:
- 1.獲取實例變量teacher的內(nèi)存地址,聲明成非托管對象
- 2.綁定到結(jié)構(gòu)體內(nèi)存,返回值是UnsafeMutablePointer<T>
- 3.訪問成員變量
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let teacher = Teacher(name: "陳老師")
// 通過Unmanaged指定內(nèi)存管理,類似于OC與CF的交互方式(所有權(quán)的轉(zhuǎn)換 __bridge)
// passUnretained 不增加引用計數(shù),即不需要獲取所有權(quán)
// passRetained 增加引用技術(shù),即需要獲取所有權(quán)
// toOpaque 不透明的指針
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
// bindMemory更改當前UnsafeMutableRawPointer的指針類型,綁定到具體類型值
// - 如果沒有綁定,則綁定
// - 如果已經(jīng)綁定,則重定向到 HeapObject類型上
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
print(heapObject.pointee.metadata)
print(heapObject.pointee.refCounts)
}
}
打印輸出heapObject的成員
0x0000000109186768 // metadata輸出的就是地址
3
然后繼續(xù)對matadata進行綁定
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let teacher = Teacher(name: "陳老師")
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
let metadataPtr = heapObject.pointee.metadata.bindMemory(to: Metadata.self, capacity: 1)
print(metadataPtr.pointee)
}
}
打印輸出metadata的成員值
Metadata(kind: 4536207176, superClass: _TtCs12_SwiftObject, cacheData: (4541967040, 140909287047168), data: 105553159911586, classFlags: 2, instanceAddressPoint: 0, instanceSize: 32, instanceAlignmentMask: 7, reserved: 0, classSize: 136, classAddressPoint: 16, typeDescriptor: 0x000000010e60e69c, iVarDestroyer: 0x0000000000000000)
2.3 withMemoryRebound(to: capacity: body:)(臨時更改內(nèi)存綁定類型)
當我們在給外部函數(shù)傳遞參數(shù)時,不免會有?些數(shù)據(jù)類型上的差距。如果我們進?類型轉(zhuǎn)換,必然要來回復制數(shù)據(jù);這個時候我們就可以使? withMemoryRebound(to: capacity: body:) 來臨時更 改內(nèi)存綁定類型。
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
var num = 10
withUnsafePointer(to: &num) { ptr: UnsafePointer<Int> in
testPointer(ptr)
}
}
}
func testPointer(_ p: UnsafePointer<Int8>) {
print(p)
}
此時編譯器報錯因為類型不匹配。
使用withMemoryRebound(to: capacity: body:)修改后:
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
var num = 10
let ptr = withUnsafePointer(to: &num) {$0}
ptr.withMemoryRebound(to: Int8.self, capacity: 1) { (ptr: UnsafePointer<Int8>) in
testPointer(ptr)
// 超過了閉包作用域,則不是UnsafePointer<Int8>類型
}
}
}
func testPointer(_ p: UnsafePointer<Int8>) {
print(p)
}