Swift記憶體佈局與指標操作
在講解指標之前,我們先來了解一下記憶體佈局,計算機中的記憶體都是以位元組為單位儲存的,但是大部分處理器並不是按位元組塊來存取記憶體的。它一般會以雙位元組,四位元組,8位元組,16位元組甚至32位元組為單位來存取記憶體,我們將上述這些存取單位稱為記憶體存取粒度。所以我們需要通過一種記憶體對齊方式來存取,這樣一方面可以節省記憶體,另一方面可以快速的取出對應型別的記憶體地址,為什麼這麼說,看完下面的內容,你就明白了。
我們知道計算機記憶體中的儲存單位是位元組,我們以Int32(佔4位元組)和UInt8(佔1位元組)為例,它們的記憶體結構如下:
接下來我們宣告一個結構體,來詳細的瞭解一下其在記憶體中的分配情況
struct MyStruct { let a: UInt8 = 0 let b: Int32 = 0 }
該結構體內部聲明瞭一個UInt8和Int32型別的屬性,那麼其在記憶體中的佈局如下:
接下來我們在講其在記憶體地址中,詳細的一段連續的記憶體地址的位元組佈局情況前,我們先說說對齊方式的規則,一個結構體中的成員中,以佔用位元組數最大的那個值作為該結構體MyStruct中記憶體對齊大小。因為UInt8佔1位元組,Int32佔4位元組,因此MyStruct的對齊值為4。接下來我們就看一下該結構體在記憶體中詳細的位元組分佈情況
以上就是MyStruct在記憶體中的詳細分佈情況。其記憶體對齊值alignment:4,所佔記憶體大小size:8,步長stride(當儲存在連續記憶體或 Array<T> 中時,從 T 的一個例項的開始到下一個例項的開始的位元組數):8。
注意:其所佔記憶體大小是指連續的一段記憶體空間,UInt8佔1位元組,Int32佔4位元組,又因為存在對齊方式,因此連續記憶體空間大小為:8
接下來我們調整一下MyStruct中的屬性宣告順序,讓我們再來分析一下其記憶體分佈情況
struct MyStruct { let b: Int32 = 0 let a: UInt8 = 0 }
其記憶體中的詳細布局如下:
其記憶體對齊值alignment:4,所佔記憶體大小size:5,步長stride(當儲存在連續記憶體或 Array<T> 中時,從 T 的一個例項的開始到下一個例項的開始的位元組數):8。
注意:其所佔記憶體大小是指連續的一段記憶體空間,Int32佔4位元組,UInt8佔1位元組,因此連續記憶體空間大小為:5
接下來我們再來看看對齊方式是如何進行的,我們在以上結構體中再新增一個屬性,其宣告順序如下:
struct MyStruct { let b: Int32 = 0 let a: UInt8 = 0 let c: UInt8 = 0 }
記憶體佈局如下:
其記憶體對齊值alignment:4,所佔記憶體大小size:6,步長stride(當儲存在連續記憶體或 Array<T> 中時,從 T 的一個例項的開始到下一個例項的開始的位元組數):8。
調換一下屬性宣告順序:
struct MyStruct { let a: UInt8 = 0 let c: UInt8 = 0 let b: Int32 = 0 }
記憶體佈局如下:
其記憶體對齊值alignment:4,所佔記憶體大小size:8,步長stride(當儲存在連續記憶體或 Array<T> 中時,從 T 的一個例項的開始到下一個例項的開始的位元組數):8。
再次調換順序:
struct MyStruct { let a: UInt8 = 0 let b: Int32 = 0 let c: UInt8 = 0 }
記憶體佈局如下:
其記憶體對齊值alignment:4,所佔記憶體大小size:9,步長stride(當儲存在連續記憶體或 Array<T> 中時,從 T 的一個例項的開始到下一個例項的開始的位元組數):12。
經過以上的調整順序,我們大該瞭解了其在記憶體中的佈局情況是如何通過對齊方式進行佈局的了。
因此我們對記憶體的對齊方式做個總結,元素是按照定義順序一個一個放到記憶體中去的,但並不是緊密排列的。從結構體儲存的首地址開始,每個元素放置到記憶體中時,它都會認為記憶體是按照自己的大小(通常它為1或4)來劃分的,因此元素放置的位置一定會在自己寬度的整數倍上開始,這就是所謂的記憶體對齊。根據上面事例也可以看出,當我們宣告一個結構體時,其內部宣告屬性的次序決定了其在記憶體中所佔空間的大小,因此根據對齊規則,我們可以減少宣告的結構體的記憶體大小,並且在獲取結構體例項時,也可以根據stride(步長)來快速獲取。
在swift中獲取一個型別的記憶體佈局情況可以通過MemoryLayout<T>獲取,它包括如下靜態屬性:
alignment(對齊數值),size(實際所佔記憶體大小),stride(記憶體步長)。具體呼叫方式如下:
let alignment = MemoryLayout<MyStruct>.alignment let size = MemoryLayout<MyStruct>.size let stride = MemoryLayout<MyStruct>.stride
需要注意的是,MemoryLayout<class型別>獲取的記憶體佈局是這個類的記憶體佈局,而不包括其內部屬性,因為class是引用型別。例如:
class MyClass { let b: Int32 let a: UInt8 init() { a = 0 b = 0 } }
MyClass的記憶體對齊值alignment:8而不是4,所佔記憶體大小size:8而不是5,步長stride:8。可以看出獲取class型別的記憶體佈局,與其內部的屬性無關,而是這個class所在的記憶體佈局。
講完了記憶體佈局,接下來開始講講指標操作了,學過C的都知道如何操作指標,在C語言中存在針對某一型別的指標例如int *a(一個int型別的指標),還有一種是通用型別指標void *(它可表示任何型別的指標),那麼在swift中同樣存在針對這兩種指標的操作物件。
UnsafePointer<Pointee>(某一型別的指標,通過泛型指定型別),UnsafeRawPointer(無型別的指標,也叫原始指標,對應C語言中的void *),那麼針對這兩種中的每一種,還可以再細分,具體分類如下:
1、型別指標:
UnsafePointer<Pointee>(不可變型別指標),對應C語言指標為:const char * a
UnsafeMutablePointer<Pointee>(可變型別指標),對應C語言指標為:char * a
UnsafeBufferPointer<Element>(不可變的陣列指標,即:相同型別元素的集合指標),針對C語言為:const int a[10]
UnsafeMutableBufferPointer<Element>(可變的陣列指標),針對C語言為:int a[10]
2、通用型別指標:
UnsafeRawPointer(不可變通用指標),針對C語言指標為:const void * a
UnsafeMutableRawPointer(可變的通用指標),針對C語言指標為:void * a
UnsafeRawBufferPointer(不可變陣列通用型別指標),針對C語言為:const void * a[10]
UnsafeMutableRawBufferPointer(可變陣列通用型別指標),對應C語言為:void * a[10]
3、不透明指標
OpaquePointer:不透明指標用於表示C指標,指向不能在Swift中表示的型別,例如不完整結構型別。我們可以根據一段記憶體地址建立這個不透明指標物件,例如:OpaquePointer(bitPattern: 0x600001fafac0)
4、作為指標引數的指標
AutoreleasingUnsafeMutablePointer:用於swift中的inout型別引數實現,即:被inout標記的引數,會被轉成該型別指標
指標操作物件的初始化
1、根據已有變數,獲取其指標操作物件
var a = 10 // 獲取a的不可變指標操作物件,並返回其值+1後的結果 let res = withUnsafePointer(to: &a) { (pointer: UnsafePointer<Int>) -> Int in // 因為pointer是不可變型別指標,並且可以看到pointer.pointee是隻讀的,因此我們無法改變該指標的值 return pointer.pointee + 1 // pointee取值操作,相當於C語言的,p[0] or *p } print("res: \(res)") // 11
可以看一下這個withUnsafePointer函式的定義:
public func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
從定義可以看出,該函式是通過一個閉包,將其指標操作物件返回給呼叫者的,並且這個閉包是可以丟擲異常的,這樣允許我們在閉包中處理異常錯誤時,丟擲這個錯誤。當然你也可以定義一個不丟擲異常的閉包函式,因為swift中的不丟擲異常函式是丟擲異常函式的子類,因此這樣的操作是允許的,就像我們上面的例子一樣。
我們還可以獲取一個可變指標,用於修改它的值:
var a = 10 // 獲取a的可變指標操作物件,使其值+1,並返回該指標+1之前的值 let res = withUnsafeMutablePointer(to: &a) { (pointer: UnsafeMutablePointer<Int>) -> Int in let oldV = pointer.pointee // pointee取值操作,相當於C語言的,p[0] or *p pointer.pointee += 1 return oldV } print("res: \(res), a: \(a)") // res: 10, a: 11
獲取陣列指標:
let list = [1, 2, 3, 4] let res = list.withUnsafeBufferPointer { (p: UnsafeBufferPointer<Int>) -> String in return p.map{"\($0)"}.joined(separator: ",") } print("res: \(res)") // res: 1,2,3,4
你還可以通過以下方法獲取陣列指標
var list = [1, 3, 4] // 獲取給定引數的原始位元組的緩衝區指標 let res = withUnsafeBytes(of: &list) { (p: UnsafeRawBufferPointer) -> String in // 將原始位元組,轉成指定型別 let array = p.load(as: [Int].self) return array.map{"\($0)"}.joined(separator: ",") } print("res: \(res)") // 1,3,4
獲取一個可變的陣列指標
var list = [1, 3, 4] withUnsafeMutablePointer(to: &list) { (p: UnsafeMutablePointer<[Int]>) in p.pointee.append(5) p.pointee.append(6) } print("list: \(list)") // list: [1, 3, 4, 5, 6]
2、直接分配記憶體指標(僅適用可變指標,因為記憶體分配後,需要初始化值,而不可變的指標,不能操作值)
分配一塊兒指定型別的儲存空間
let count = 3 // 分配一塊記憶體空間,該記憶體空間標記為Int型別,並且開闢了3塊兒Int型別的儲存空間 let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count) pointer.initialize(to: 10) // 給第一塊兒記憶體初始化 pointer.successor().pointee = 12 // 為第二塊兒記憶體初始化 (pointer + 2).initialize(to: 20) // 為第三塊兒記憶體初始化 print("val1: \(pointer.pointee)") // 取第一塊兒記憶體值 10 print("val2: \(pointer.successor().pointee)") // 取第二塊兒記憶體值 12 print("val3: \((pointer + 2).pointee)") // 取第三塊兒記憶體值 20 pointer.deallocate() // 在不需要這塊兒記憶體時,需要我們手動釋放
分配一塊兒不指定型別的儲存空間
struct People: CustomStringConvertible { let name: String let age: Int8 var description: String{ "name: \(name), age: \(age)" } } let count = 3 let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<People>.stride * count, // 注意:這裡必須是型別記憶體步長的整數倍數 alignment: MemoryLayout<People>.alignment) let peopleListPoint = pointer.bindMemory(to: People.self, capacity: count) // 為這塊記憶體標記型別,這樣在下面為記憶體賦值時,就可以直接用該型別的值進行初始化 // peopleListPoint變成了UnsafeMutablePointer<People>型別 peopleListPoint.initialize(to: People(name: "drbox", age: 24)) // 初始化第一塊兒記憶體值 peopleListPoint.successor().initialize(to: People(name: "boss", age: 30)) // 初始化第二塊兒記憶體值 (peopleListPoint + 2).initialize(to: People(name: "jack", age: 40)) // 初始化第三塊兒記憶體值 for i in 0..<count { let people = peopleListPoint[i] print(people) } // 這塊兒記憶體不用了,需要手動釋放掉 peopleListPoint.deinitialize(count: count) peopleListPoint.deallocate()
你也可以通過如下方式分配一塊兒記憶體
let ptr = malloc(24) // 分配一塊兒容量為24位元組的記憶體空間,並返回該空間地址的指標,指標型別為:UnsafeMutableRawPointer? ptr?.storeBytes(of: 100, as: Int.self) // 在前8位(Int型別為8位),儲存一個100的值 ptr?.storeBytes(of: 200, toByteOffset: MemoryLayout<Int>.stride, as: Int.self) // 在8到16位的記憶體地址,儲存一個200 ptr?.storeBytes(of: 300, toByteOffset: MemoryLayout<Int>.stride * 2, as: Int.self) // 在16到24位的記憶體地址,儲存一個300 // 取值 if let v = ptr?.load(as: Int.self) { print("前8位儲存的值:\(v)") // 前8位儲存的值:100 } if let v = ptr?.load(fromByteOffset: MemoryLayout<Int>.stride, as: Int.self) { print("前8至16位儲存的值:\(v)") // 前8至16位儲存的值:200 } if let v = ptr?.load(fromByteOffset: MemoryLayout<Int>.stride * 2, as: Int.self) { print("前16至24位儲存的值:\(v)") // 前16至24位儲存的值:300 } // 記憶體不需要時,釋放記憶體 ptr?.deallocate()
從無型別指標(原始指標)中獲取對應位置的記憶體值
var a = (1, "a", 3) // 記憶體地址佈局:Int、String、Int。轉成位元組:(8位元組)xxxxxxxx、(16位元組)xxxxxxxxxxxxxxxx、(8位元組)xxxxxxxx withUnsafePointer(to: &a) { (p: UnsafePointer<(Int, String, Int)>) in // 將型別指標轉成原始指標 let rawPointer = UnsafeRawPointer(p) // 這裡我們可以通過原始指標的load方法來取對應位置的記憶體中的值,例如:rawPointer記憶體中的前8個位元組為Int型別,因此可以如下獲取整個值: let intVal1 = rawPointer.load(as: Int.self) // 注意,這裡不要寫錯型別,如果這裡指定的型別與記憶體中實際繫結的型別不匹配,將導致crash // 獲取該指標首地址的值,我們還可以將首地址轉成一個指定型別的指標,來獲取其指標的值 let intPointer = rawPointer.assumingMemoryBound(to: Int.self) // intPointer為UnsafePointer<Int>型別 print("intPointer.val: \(intPointer.pointee)") // intPointer.val: 1 // 在獲取記憶體中的下一個值時,我們需要移動當前指標的首地址,到下一個值的位置,load的另一個方法允許我們獲取從當前指標首地址,偏移指定步長的記憶體值 // 這我們通過MemoryLayout<Int>.stride來獲取當前指標首地址記憶體型別的步長,因此下面方法獲取的記憶體為8-32位元組的記憶體 let strVal = rawPointer.load(fromByteOffset: MemoryLayout<Int>.stride, as: String.self) // 當然我們也可以通過移動指標來實現,原始指標為我們提供了移動指標的方法,使其返回一個移動後的新指標 let newPointer = rawPointer.advanced(by: MemoryLayout<Int>.stride) // 這裡指定移動8位元組步長,得到的是指向8-32位元組的新的原始指標 let str2Val = newPointer.load(as: String.self) // 這樣我們就可以直接取該指標的首地址,並轉成對應型別的值 // 接下來獲取記憶體中最後一個值,該值為Int型別,因此我們可以在這個newPointer指標的基礎上獲取該值 let intVal2 = newPointer.load(fromByteOffset: MemoryLayout<String>.stride, as: Int.self) print("intVal1: \(intVal1)") // intVal1: 1 print("strVal: \(strVal)") // strVal: a print("str2Val: \(str2Val)") // str2Val: a print("intVal2: \(intVal2)") // intVal2: 3 }
取結構體型別的記憶體指標的對應屬性值
struct People: CustomStringConvertible { let name: String let age: Int8 var description: String{ "name: \(name), age: \(age)" } } var people = People(name: "drbox", age: 24) withUnsafePointer(to: &people) { (p: UnsafePointer<People>) in //將型別指標,轉成原始指標 let rawPointer = UnsafeRawPointer(p) let name = rawPointer.load(as: String.self) print("name: \(name)") // name: drbox // 通過記憶體佈局物件,來獲取結構體內,對應位置的屬性在記憶體地址中的偏移量 if let offset = MemoryLayout<People>.offset(of: \People.age){ let age = rawPointer.load(fromByteOffset: offset, as: Int8.self) print("age: \(age)") // age: 24 // 除了load方式取值,我們還可以通過移動指標來取值,移動指標除了advanced(by:),我們還可以通過+操作符來移動原始指標,並返回一個新的原始指標,這就類似於C語言中的+一樣 let newPointer = rawPointer + offset let age2 = newPointer.assumingMemoryBound(to: Int8.self).pointee print("age2: \(age2)") // age: 24 } }
記憶體型別繫結,也可以認為是型別轉換,包括以下幾個函式:
func assumingMemoryBound<T>(to: T.Type) -> UnsafeMutablePointer<T> func bindMemory<T>(to type: T.Type, capacity count: Int) -> UnsafeMutablePointer<T> func withMemoryRebound<T, Result>(to type: T.Type, capacity count: Int, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
assmuingMemoryBound(to:):極少數情況下,程式碼沒有保留型別指標,只有原始指標,但明確知道記憶體繫結的型別,這時候就需要assmuingMemoryBound(to:)
來告訴編譯器預期型別,轉換成對應的型別指標(注意:這裡的轉換隻是讓編譯器繞過型別檢查,並沒有實際發生轉換)。
// 初始化一個原始指標(C:void *) let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) // 向其對應記憶體空間中儲存一個Int型別的值 pointer.initializeMemory(as: Int.self, repeating: 10, count: 1) // 此時如果我們需要操作這塊兒記憶體空間時,需要使用其對應型別的指標,我們可以通過以下方法轉換到指定型別指標 let int8Pointer = pointer.assumingMemoryBound(to: Int.self) // int8Pointer即為UnsafeMutablePointer<Int>型別 print("int8Value: \(int8Pointer.pointee)") // int8Value: 10 // 在呼叫以上函式進行指標型別轉換時,前提是我們明確知道其原始指標記憶體型別,否則我們在轉換一個與原始記憶體型別不同的指標型別時,會出現不可預料的錯誤 let strPointer = pointer.assumingMemoryBound(to: String.self) print("strValue: \(strPointer.pointee)") // 這裡將原始指標記憶體型別為Int,轉成String型別指標時,strPointer會轉換失敗,呼叫strPointer.pointee時可能會導致crash
bindMemory(to:capacity:):將記憶體繫結到指定型別,並返回指向繫結記憶體的型別化指標
。如果記憶體還沒有型別繫結,則將首次繫結為該型別。如果記憶體已經進行型別繫結,則將重新繫結為該型別,並且記憶體裡所有值都會變成該型別。
let count = 4 // 初始化一個以Int8對齊值為對齊標準值(1位元組),分配100個位元組的空間 let bytesPointer = UnsafeMutableRawPointer.allocate(byteCount: 100, alignment: MemoryLayout<Int8>.alignment) // 將前4個位元組繫結為Int8型別 let int8Pointer = bytesPointer.bindMemory(to: Int8.self, capacity: count) int8Pointer.pointee = 10 // 首位元組初始值 int8Pointer.successor().pointee = 20 // 第二個位元組初始值 int8Pointer.successor().successor().pointee = 30 // 第三個位元組初始值 int8Pointer.successor().successor().successor().pointee = 40 // 第四個位元組初始值 // 因為bytesPointer的對齊值為1,即記憶體跨度為1,因此上面的初始化操作後,第一個位元組儲存了10,第二個位元組儲存了20,第三個位元組儲存了30,第四個位元組儲存了40 // 取出第一個位元組儲存的值 let int8Val1 = bytesPointer.load(as: Int8.self) print("int8Val1: \(int8Val1)") // int8Val1: 10 // 取出第二個位元組儲存的值 let int8Val2 = bytesPointer.load(fromByteOffset: MemoryLayout<Int8>.stride, as: Int8.self) print("int8Val2: \(int8Val2)") // int8Val2: 20 // 取出第三個位元組儲存的值 let int8Val3 = bytesPointer.load(fromByteOffset: MemoryLayout<Int8>.stride * 2, as: Int8.self) print("int8Val3: \(int8Val3)") // int8Val3: 30 // 取出第四個位元組儲存的值 let int8Val4 = bytesPointer.load(fromByteOffset: MemoryLayout<Int8>.stride * 3, as: Int8.self) print("int8Val4: \(int8Val4)") // int8Val4: 40 // 記憶體不用時,記得回收 bytesPointer.deallocate()
withMemoryRebound(to:capacity:body)
:臨時更改記憶體繫結的型別,它的作用域只在閉包內。閉包返回時,將會重新繫結為原始型別。這可以將臨時型別指標的訪問和其他程式碼的作用域分開。但是它有一些嚴格的限制:
- 需要有原始指標
- 轉換的型別和原始的型別需要有相同的跨度,即步長
var a: Int8 = 100 withUnsafePointer(to: &a) { (p: UnsafePointer<Int8>) in // 將Int8型別的指標,臨時轉成UInt8型別指標,因為Int8與UInt8有著相同的跨度,因此這個轉換是被允許的 p.withMemoryRebound(to: UInt8.self, capacity: 1) { (ptr: UnsafePointer<UInt8>) in print("int8 convert to uint8: \(ptr.pointee)") } }
Unmanaged(非託管物件):如果直接使用指標,那麼就需要我們手動管理記憶體,這個並不好辦,所以蘋果引入了Unmanaged來管理引用計數,Unmanaged 能夠將由 C API 傳遞過來的指標進行託管,我們可以通過Unmanaged標定它是否接受引用計數的分配,以便實現類似自動釋放的效果;同時,如果不是使用引用計數,也可以使用Unmanaged 提供的release函式來手動釋放,這比在指標中進行這些操作要簡單很多。
我們知道在OC中使用指標時,也涉及到記憶體管理,它是通過__bridge(只做型別轉換,不擁有物件)、__bridge_transfer(轉換型別,並釋放原本物件的擁有權,將擁有權交給轉換後的物件)、__bridge_retained(轉換型別,並擁有其所有權)
那麼在swift中就是通過Unmanaged來實現的,但是它只能轉化AnyObject的class型別
public struct Unmanaged<Instance> where Instance : AnyObject
Unmanaged有三個static型別的初始化方法:
// 通過class的原始指標,初始化(不會增加該class的引用計數,引用計數不變) static func fromOpaque(_ value: UnsafeRawPointer) -> Unmanaged<Instance> // 通過一個例項物件,初始化(會增加該class例項的引用計數) static func passRetained(_ value: Instance) -> Unmanaged<Instance> // 通過一個例項物件,初始化(不會增加該class例項的引用計數) static func passUnretained(_ value: Instance) -> Unmanaged<Instance>
然後通過Unmanaged例項可以將class例項轉成UnsafeMuableRawPointer,並且通過以下方法拿到這個例項物件
// 返回class例項物件,並不會讓Unmanaged對其之前的引用-1 func takeUnretainedValue() -> Instance // 返回class例項物件,並讓Unmanaged對其之前的引用-1 func takeRetainedValue() -> Instance
當然它還提供了以下方法,用於維護class例項物件的引用計數
// 引用計數+1 func retain() -> Unmanaged<Instance> // 引用計數-1 func release() // 自動引用計數 func autorelease() -> Unmanaged<Instance>
具體我們看一個例子,首先我們宣告一個People類,注意:它是一個class型別的
class People { let name: String let age: Int8 init(name: String, age: Int8) { self.name = name self.age = age } deinit { print("People deinit") } }
然後宣告一個臨時持有People指標的類
class Monitor { static let share = Monitor() var ctx: UnsafeRawPointer? private var callback: ((UnsafeRawPointer?) -> Void)? func startMonitor(_ callback: @escaping (UnsafeRawPointer?)->Void) { self.callback = callback run() } private func run(){ DispatchQueue.main.asyncAfter(deadline: .now()+3) { if let call = self.callback{ call(self.ctx) } } } }
然後具體使用如下:
let people = People(name: "drbox", age: 24) // 因為people是區域性變數,因此它在超出當前方法作用域後就會被釋放,所以我們在將其指標傳給Monitor物件的屬性時,需要呼叫passRetained,來增加其引用計數,這樣people就不會在方法執行完後而釋放了 Monitor.share.ctx = UnsafeRawPointer(Unmanaged.passRetained(people).toOpaque()) Monitor.share.startMonitor { (ptr: UnsafeRawPointer?) -> Void in if let p = ptr{ // 3秒後會回撥這個閉包函式,並將上面的people指標傳回,ptr即為上面people的指標 let pp = Unmanaged<People>.fromOpaque(p).takeRetainedValue()// 由於上面對people增加了引用計數,因此這裡在取people的值時,需要呼叫takeRetainedValue來對引用計數-1,這樣一來people就會釋放了 print("name: \(pp.name), age: \(pp.age)") } }
輸出日誌如下:
name: drbox, age: 24 People deinit
另一種實現方式如下:
let people = People(name: "drbox", age: 24) let um = Unmanaged.passUnretained(people) // 這裡不會對people增加引用計數 // 因此需要手動呼叫retain增加引用計數 _ = um.retain() Monitor.share.ctx = UnsafeRawPointer(um.toOpaque()) Monitor.share.startMonitor { (ptr: UnsafeRawPointer?) -> Void in if let p = ptr{ // 3秒後會回撥這個閉包函式,並將上面的people指標傳回,ptr即為上面people的指標 let pp = Unmanaged<People>.fromOpaque(p).takeUnretainedValue()// takeUnretainedValue取出people例項,但不會對引用計數-1 print("name: \(pp.name), age: \(pp.age)") // 因此在我們不在用這個people例項後,需要呼叫其release對引用計數-1 Unmanaged<People>.fromOpaque(p).release() } }
輸出日誌:
name: drbox, age: 24 People deinit要得到你必須要付出,要付出你還要學會堅持,如果你真的覺得很難,那你就放棄,但是你放棄了就不要抱怨,我覺得人生就是這樣,世界真的是平等的,每個人都要通過自己的努力,去決定自己生活的樣子。