Swift底層探索(三):指針

iOS內(nèi)存分區(qū)

image.png

Stack

棧區(qū)就是存放當(dāng)前:局部變量和函數(shù)運行過程中的上下文。

func test() {
    var age: Int = 10
    print(age)
}

test()
(lldb) po withUnsafePointer(to: &age){print($0)}
0x00007ffeefbff3d8
0 elements

(lldb) cat address 0x00007ffeefbff3d8
&0x00007ffeefbff3d8, stack address (SP: 0x7ffeefbff3b0 FP: 0x7ffeefbff3e0) SwiftPointer.test() -> ()
(lldb)

直接定義一個局部變量,然后lldb查看一下,可以看到是一個Stack address

Heap

對于堆空間,通過new & malloc關(guān)鍵字來申請內(nèi)存空間,不連續(xù),類似鏈表的數(shù)據(jù)結(jié)構(gòu)。

class HotpotCat {
    var age: Int = 18
}

func test() {
    var hotpot = HotpotCat()
    print(hotpot)
}

test()
(lldb) po hotpot
<HotpotCat: 0x101157190>

(lldb) cat address 0x101157190
&0x101157190, (char *) $4 = 0x00007ffeefbff250 "0x101157190 heap pointer, (0x20 bytes), zone: 0x7fff88aa8000"
  • hotpot變量存放在棧區(qū)
  • hotpot變量中存放的地址位于堆區(qū)

全局區(qū)(靜態(tài)區(qū))

//全局已初始化變量
int age = 10;
//全局未初始化變量
int age1;

//全局靜態(tài)變量
static int age2 = 30;

int main(int argc, const char * argv[]) {
    // insert code here...
    char *p = "Hotpot";
    printf("%d",age);
    printf("%d",age1);
    printf("%d",age2);
    return 0;
}
(lldb) po &age
0x0000000100008010

(lldb) cat address 0x0000000100008010
&0x0000000100008010,  age <+0> CTest.__DATA.__data
(lldb) 

age位于可執(zhí)行文件__DATA.__data

image.png

(lldb) po &age1
0x0000000100008018

(lldb) cat address 0x0000000100008018
&0x0000000100008018,  age1 <+0> CTest.__DATA.__common

age1位于__DATA.__common中。這里將未初始化和已初始化變量分開存放(劃分更細),便于定位符號。

//如果不調(diào)用age2,po &會找不到地址。
(lldb) po &age2
0x0000000100008014

(lldb) cat address 0x0000000100008014

age2位于__DATA.__data中。
仔細觀察3個變量的地址,可以看到ageage2離額比較近。這也就驗證了全局區(qū)分開存儲了已初始化和未初始化比變量。age1地址比較大,所以
在全局區(qū)中,已初始化變量位于未初始化變量下方(按地址大小)

&age
0x0000000100008010
 &age1
0x0000000100008018
age2
0x0000000100008014

常量區(qū)

//全局靜態(tài)常量
static const int age3 = 30;

int main(int argc, const char * argv[]) {
    int age4 = age3;
    return 0;
}

(lldb) po &age3
0x00000001004f8440

(lldb) cat address 0x00000001004f8440
Couldn't find address, reverting to "image lookup -a 0x00000001004f8440"
(lldb) 

接著上面的例子,定義一個static const修飾的age3,發(fā)現(xiàn)查找不到地址,斷點調(diào)試發(fā)現(xiàn)直接賦值的是30。也就是沒有age3這個符號,直接存儲的是30這個值。

image.png

那么去掉static

(lldb) po &age3
0x0000000100003fa0

(lldb) cat address 0x0000000100003fa0
&0x0000000100003fa0,  age3 <+0> CTest.__TEXT.__const

發(fā)現(xiàn)age3__TEXT.__const

image.png

再驗證下*p
image.png

__text

就是我們當(dāng)前執(zhí)行的指令。

swift中,調(diào)試的時候全局變量初始化前在__common段,初始化后還是在__common段,按理說應(yīng)該在__data段的。

總結(jié):分區(qū)(人為的籠統(tǒng)的劃分)和 macho文件不是一個東西(放在segmentsection中,符號劃分更細)。可以簡單理解為WindowsMacOS

方法調(diào)度

結(jié)構(gòu)體 靜態(tài)調(diào)用(直接調(diào)用)。在編譯完成后方法的地址就確定了,在執(zhí)行代碼的過程中就直接跳轉(zhuǎn)到地址執(zhí)行方法。對于類中的方法存儲在V-Table中,執(zhí)行的時候去V-table中找。

V-Table(函數(shù)表)

V-table是一個數(shù)組結(jié)構(gòu)擴展中是直接調(diào)用SIL中是這樣表示的:

decl ::= sil-vtable
sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-name

identifier標(biāo)識就是類, sil-decl-ref聲明,sil-function-name方法名稱。
直接看一個SIL文件

class HotpotCat {
  func test()
  func test1()
  func test2()
  func test3()
  init()
  @objc deinit
}

sil_vtable HotpotCat {
  #HotpotCat.test: (HotpotCat) -> () -> () : @main.HotpotCat.test() -> ()   // HotpotCat.test()
  #HotpotCat.test1: (HotpotCat) -> () -> () : @main.HotpotCat.test1() -> () // HotpotCat.test1()
  #HotpotCat.test2: (HotpotCat) -> () -> () : @main.HotpotCat.test2() -> () // HotpotCat.test2()
  #HotpotCat.test3: (HotpotCat) -> () -> () : @main.HotpotCat.test3() -> () // HotpotCat.test3()
  #HotpotCat.init!allocator: (HotpotCat.Type) -> () -> HotpotCat : @main.HotpotCat.__allocating_init() -> main.HotpotCat    // HotpotCat.__allocating_init()
  #HotpotCat.deinit!deallocator: @main.HotpotCat.__deallocating_deinit  // HotpotCat.__deallocating_deinit
}

sil_vtable是關(guān)鍵字,HotpotCat代表是HotpotCat class的函數(shù)表。這張表本質(zhì)就是數(shù)組,在聲明class內(nèi)部的方法時不加任何關(guān)鍵字修飾的時候就連續(xù)存放在我們當(dāng)前的地址空間中。

斷點觀察

斷點直觀觀察一下,先熟悉一下匯編指令:

ARM64匯編指令

  • blr:帶返回的跳轉(zhuǎn)指令,跳轉(zhuǎn)到指令后邊跟隨寄存器中保存的地址
  • mov:將某一寄存器的值復(fù)制到另一寄存器(只能用于寄存器與寄存器/寄存器與常量之間傳值,不能用于內(nèi)存地址)
    如:mov x1,x0 將寄存器x0的值復(fù)制到寄存器x1中。
  • ldr:將內(nèi)存中的值讀取到寄存器
    如:ldr x0, [x1, x2] 將寄存器 x1 和寄存器 x2 相加作為地址,取該內(nèi)存地址的值放入 x0 中。
  • str:將寄存器中的值寫入到內(nèi)存中
    如: str x0, [x0, x8] 將寄存器x0的值保存到內(nèi)存[x0 + x8]處。
  • bl: 跳轉(zhuǎn)到某地址

直接斷點調(diào)試


image.png

x9地址是從x8偏移0x60獲取的,讀取下x8內(nèi)容,x8就是hotpot

(lldb) register read x8
      x8 = 0x00000002837ad610
(lldb) x/8g 0x00000002837ad610
0x2837ad610: 0x0000000104b4dca8 0x0000000200000003
0x2837ad620: 0x000098b3fd5dd620 0x00000002097b0164
0x2837ad630: 0x000098b3fd5dd630 0x0000000209770165
0x2837ad640: 0x000098b3fd5dd640 0x0000000209770167
(lldb) 
image.png
  • hotpot地址偏移x60然后跳轉(zhuǎn)。x60就是test2


    image.png

    可以看到test、test1、test2、test3是連續(xù)的地址,這里也就驗證了class中方法是連續(xù)存放的。

源碼觀察

搜索initClassVTable并打個斷點

image.png

  if (description->hasVTable()) {
    auto *vtable = description->getVTableDescriptor();
    auto vtableOffset = vtable->getVTableOffset(description);
    auto descriptors = description->getMethodDescriptors();
    for (unsigned i = 0, e = vtable->VTableSize; i < e; ++i) {
      auto &methodDescription = descriptors[i];
      swift_ptrauth_init(&classWords[vtableOffset + i],
                         methodDescription.Impl.get(),
                         methodDescription.Flags.getExtraDiscriminator());
    }
  }

從源碼中可以看到直接是一個for循環(huán)把V-Table寫到內(nèi)存中。大小為vtable->VTableSize,偏移量是vtableOffset。從description->getMethodDescriptors()獲取放到classWords中。

extension中方法調(diào)用

extension HotpotCat {
    func test4() {
        print("test4")
    }
}

image.png

可以看出變成了直接調(diào)用。
why?

class HotpotCat {
    func test() {
        print("test")
    }
    func test1() {
        print("test1")
    }
    func test2() {
        print("test2")
    }
    func test3() {
        print("test3")
    }
}

extension HotpotCat {
    func test4() {
        print("test4")
    }
}

class HotpotCatChild: HotpotCat {
    func test5() {
        print("test5")
    }
}

HotpotCatChild繼承自HotpotCat 我們直接看一下SIL文件.

image.png

可以看到HotpotCatChild繼承了HotpotCat所有的方法,并且在父類方法后插入了自己的test5

假如extension也放在V-Table中,那么extension有可能放在任意一個文件中,那編譯的時候只有編譯到的時候才知道有extension。此時擴展中的方法可以插入到父類,但無法插入到子類V-Table中。因為沒有指針記錄哪塊是父類方法,哪塊是子類方法,并且繼承層級可能很多,加指針記錄也不現(xiàn)實。(OC是通過移動內(nèi)存地址合成的)。

OC方法列表合成

這也就是OC category方法會"覆蓋"同名方法的原因。


image.png
  • V-Table是一個數(shù)組的結(jié)構(gòu)
  • extension中方法變成了靜態(tài)調(diào)用

靜態(tài)調(diào)用與V-Table區(qū)別

  • 靜態(tài)調(diào)用直接調(diào)用地址。
  • V-Table需要先找到基地址,然后基地址根據(jù)偏移量找到函數(shù)地址。
    調(diào)用速度:靜態(tài)調(diào)用>V-Table>動態(tài)調(diào)用

SIL使用 class_method, super_method, objc_method, 和 objc_super_method 操作來實現(xiàn)類方法的動態(tài)分派.

還有OverrideTable、PWT(Protocol Witness Table)、VWT(Value Witness Table)。VWT和PWT通過分工,去管理Protocol Type實例的內(nèi)存管理(初始化,拷貝,銷毀)和方法調(diào)用。

final

final修飾方法就變成直接調(diào)用了。

  • final 修飾類(只能修飾類),這個類就不能被繼承;
  • final 修飾方法,方法不能被重寫(override);
  • static 和 final 不能一起使用, static修飾的就已經(jīng)是final了

@objc

標(biāo)記給OC調(diào)用,并沒有修改調(diào)度方式。
由于Swift是靜態(tài)語言,OC是動態(tài)語言。在混合項目中為了保證安全將swift中可能被OC調(diào)用的方法加上@objc標(biāo)記。

  • @objc標(biāo)記
  • 繼承自NSObject。(swift3的時候編譯器默認給繼承自NSObject的類所有方法添加@objc,swift4之后只會給實現(xiàn)OC接口和重寫OC方法的函數(shù)添加)

這樣就可以優(yōu)化刪除沒有用到的swift函數(shù),減少包大小。
https://www.hangge.com/blog/cache/detail_1839.html

編譯器會自己生成轉(zhuǎn)換文件


image.png
SWIFT_CLASS("_TtC7SwiftOC9HotpotCat")
@interface HotpotCat : NSObject
- (void)test1;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
class HotpotCat {
    @objc func test(){
        print("test")
    }
}

var hotpot = HotpotCat()
hotpot.test()

SIL文件查看一下這個過程究竟做了什么處理


image.png

@objc標(biāo)記的方法生成了兩個方法,一個是swift原有的方法,另一個是生成供oc調(diào)用的方法,oc調(diào)用的這個方法內(nèi)部調(diào)用了swift的方法
更詳細資料:https://blog.csdn.net/weixin_33816300/article/details/91370145

dynamic

class HotpotCat {
    dynamic func test(){
        print("test")
    }
}

var hotpot = HotpotCat()
hotpot.test()
image.png

可以看到依然是函數(shù)表調(diào)度。

  • dynamic不改變調(diào)度方式
  • dynamic使屬性啟用Objc的動態(tài)轉(zhuǎn)發(fā)功能;
  • dynamic只用于類,不能用于結(jié)構(gòu)體和枚舉,因為它們沒有繼承機制,而Objc的動態(tài)轉(zhuǎn)發(fā)就是根據(jù)繼承關(guān)系來實現(xiàn)轉(zhuǎn)發(fā)。
  • Swift 中我們也是可以使用 KVO 的,但是僅限于在 NSObject 的子類中。這是可以理解的,因為 KVO 是基于 KVC (Key-Value Coding) 以及動態(tài)派發(fā)技術(shù)實現(xiàn)的,而這些東西都是 Objective-C 運行時的概念。另外由于 Swift 為了效率,默認禁用了動態(tài)派發(fā),因此想用 Swift 來實現(xiàn) KVO,我們還需要做額外的工作,那就是將想要觀測的對象標(biāo)記為 dynamic。
  • OC中@dynamic關(guān)鍵字告訴編譯器不要為屬性合成getter和setter方法。
  • Swift中dynamic關(guān)鍵字,可以用于修飾變量或函數(shù),意思與OC完全不同。它告訴編譯器使用動態(tài)分發(fā)而不是靜態(tài)分發(fā)。
  • 標(biāo)記為dynamic的變量/函數(shù)會隱式的加上@objc關(guān)鍵字,它會使用OC的runtime機制。
    https://www.cnblogs.com/feng9exe/p/9084788.html
    http://www.lxweimin.com/p/49b8e6f6a51d

Swift替換

class HotpotCat {
    dynamic func test(){
        print("test")
    }
}

extension HotpotCat {
    @_dynamicReplacement(for: test)
    func test1(){
        print("test1")
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        var hotpot = HotpotCat()
        hotpot.test()
    }
}

lldb
test1

這里調(diào)用test實際上調(diào)用到了test1。

既然可以替換方法,那么能不能替換屬性呢?
首先要清楚extension中不能添加存儲屬性。只能添加計算屬性。而要更改屬性的行為也就是更改setget方法就好了。所以這接給擴展添加一個存儲屬性并綁定到age 上:

class HotpotCat {
    dynamic var age: Int = 18
    dynamic func test() {
        print("test")
    }
}

extension HotpotCat {
    @_dynamicReplacement(for: age)
    var age2: Int {
        set {
            age = newValue
        }
        get {
            10
        }
    }
    @_dynamicReplacement(for: test)
    func test1() {
        print("test1")
    }
}
var hotpot = HotpotCat()

hotpot.test()

print(hotpot.age)

輸出

test1
10
(lldb) 

@objc+dynamic

class HotpotCat {
    @objc  dynamic func test(){
        print("test")
    }
}
image.png

可以看到調(diào)度方式已經(jīng)變成了動態(tài)轉(zhuǎn)發(fā)。

指針

Swift中指針分為raw pointer 未指定數(shù)據(jù)類型指針和typed pointer指定數(shù)據(jù)類型指針。

  • raw pointer表示UnsafeRawPointer
  • typed pointer表示UnsafePointer<T>

Swift指針與OC對應(yīng)關(guān)系:

Swift Objective-c 說明
unsafeRawPointer const void * 指針及指向的內(nèi)存內(nèi)容(未知)均不可變
unsafeMutableRawPointer void * 指針指向未知內(nèi)容
unsafePointer<T> const T * 指針及所指內(nèi)容都不可變
unsafeMutablePointer<T> T * 指針及所指向的內(nèi)存內(nèi)容均可變

raw pointer(原始指針)

假如我們想在內(nèi)存中存儲連續(xù)4個整形的數(shù)據(jù),用raw pointer來處理

//手動管理內(nèi)存,allocate要與deallocate匹配
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

for i in 0 ..< 4 {
    //advanced 步長,相當(dāng)于p前進 i*8 存儲 i + 1
    p.advanced(by: i * 8).storeBytes(of: i + 1, 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()

UnsafeMutableRawPointer源碼

image.png

BuiltinSwift標(biāo)準(zhǔn)模塊,匹配LLVM的類型和方法減少swift運行時負擔(dān)。

typed pointer(指定數(shù)據(jù)類型指針)

typed pointer簡單用法
方式一

var age = 18

//p的值由閉包表達式返回值決定
let p = withUnsafePointer(to: &age) {$0}
//pointee 相當(dāng)于 *p = age
print(p.pointee)

//UnsafePointer中不能修改pointee
age = withUnsafePointer(to: &age) { p in
    p.pointee + 10
}
print(age)
//UnsafeMutablePointer中可以修改pointee
withUnsafeMutablePointer(to: &age) { p in
    p.pointee += 10
}

print(age)

方式二

//容量為1也就意味著8字節(jié)
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
//initialize 與 deinitialize 對應(yīng),allocate 與 deallocate 對應(yīng)
ptr.initialize(to: age)

ptr.deinitialize(count: 1)

ptr.pointee += 10

print(ptr.pointee)

ptr.deallocate()

取值或者創(chuàng)建兩種方式都可以,只不過第一種方式age的值被改變了,第二種方式age值并沒有被改變。

指針創(chuàng)建結(jié)構(gòu)體

struct HPTeacher {
    var age = 10
    var height = 1.85
}

//指針創(chuàng)建HPTeacher
let ptr = UnsafeMutablePointer<HPTeacher>.allocate(capacity: 2)
ptr.initialize(to: HPTeacher())
//這里advanced為1是因為這里ptr是type pointer。知道類型,不需要我們傳大小了。只需要傳步數(shù)就好了。
ptr.advanced(by: 1).initialize(to: HPTeacher(age: 18, height: 1.80))

print(ptr[0])
print(ptr[1])

print(ptr.pointee)
print((ptr + 1).pointee)
//successor 本質(zhì)目的往前移動等效(ptr + 1).pointee
print(ptr.successor().pointee)

ptr.deinitialize(count: 2)
ptr.deallocate()
  • 對指針的內(nèi)存管理需要我們手動管理,allocatedeallocate匹配,initializedeinitialize 匹配。

應(yīng)用

HeapObject(實例對象)結(jié)構(gòu)

struct HeapObject {
    var kind: UnsafeRawPointer
    var strongRef: UInt32
    var unownedRef: uint32
}

class HPTeacher {
    var age = 18
}

//實例變量內(nèi)存地址
var t = HPTeacher()

//let ptr1 = withUnsafePointer(to: &t){$0}
//print(t)//SwiftPointer.HPteacher
//print(ptr1)//0x00000001000081e0
//print(ptr.pointee)//SwiftPointer.HPteacher

//這里拿到的就是實例對象的值。因為Raw pointer  和 type pointer都需要我們自己管理內(nèi)存。 Unmanaged可以托管指針。
//passUnretained 這里有點類似OC和C的交互 __bridge 所有權(quán)的轉(zhuǎn)換。
//toOpaque返回不透明的指針UnsafeMutableRawPointer
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
//Raw pointer 重新綁定為 HeapObject 的 UnsafeMutablePointer
//bindMemory更改ptr的指針類型綁定到HeapObject具體的內(nèi)存指針。如果ptr沒有綁定則首次綁定到HeapObject,如果綁定過了會重新綁定。
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)

print(heapObject.pointee.kind)
print(heapObject.pointee.strongRef)
print(heapObject.pointee.unownedRef)

這里tptr的區(qū)別:

(lldb) po withUnsafePointer(to: &t){print($0)}
0x00000001000081e0
0 elements

(lldb) po ptr
? 0x0000000101817860
  - pointerValue : 4320229472

(lldb) x/8g 0x00000001000081e0
0x1000081e0: 0x0000000101817860 0x0000000101817860
0x1000081f0: 0x0000000101817860 0x0000000000000000
0x100008200: 0x0000000000000000 0x0000000000000000
0x100008210: 0x0000000000000000 0x0000000000000000
(lldb) 

相當(dāng)于ptr指向t的內(nèi)存地址的metadata地址。

類結(jié)構(gòu)

struct HeapObject {
    var kind: UnsafeRawPointer
    var strongRef: UInt32
    var unownedRef: uint32
}

struct hp_swift_class {
    var kind: UnsafeRawPointer
    var superClass:UnsafeRawPointer
    var cacheData1:UnsafeRawPointer
    var cacheData2:UnsafeRawPointer
    var data:UnsafeRawPointer
    var flags:UInt32
    var instanceAddressOffset:UInt32
    var instanceSize:UInt32
    var instanceAlignMask:UInt16
    var reserved:UInt16
    var classSize:UInt32
    var classAddressOffset:UInt32
    var description:UnsafeRawPointer
}

class HPTeacher {
    var age = 18
}

var t = HPTeacher()
//獲取HPTeacher的指針
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
//綁定 ptr 到 HeapObject(實例對象)
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
//綁定 heapObject 的 kind(metadata) 到 hp_swift_class(類)。這里本質(zhì)上內(nèi)存結(jié)構(gòu)一致,所以能正確拿到數(shù)據(jù)。
let metaPtr = heapObject.pointee.kind.bindMemory(to: hp_swift_class.self, capacity: 1)

print(metaPtr.pointee)

可以看到打印信息如下:

hp_swift_class(kind: 0x0000000100008140, superClass: 0x00007fff88a6f6f8, cacheData1: 0x00007fff201d3af0, cacheData2: 0x0000802000000000, data: 0x000000010054b672, flags: 2, instanceAddressOffset: 0, instanceSize: 24, instanceAlignMask: 7, reserved: 0, classSize: 136, classAddressOffset: 16, description: 0x0000000100003c3c)

指針轉(zhuǎn)換

元組指針轉(zhuǎn)換

var tul = (10,20)

func testPointer(_ p : UnsafePointer<Int>) {
    print(p.pointee)
    print(p[1])
}


withUnsafePointer(to: &tul) { (tulPtr : UnsafePointer<(Int,Int)>) in
    //assumingMemoryBound 假定內(nèi)存綁定,告訴編譯器 tulPtr 已經(jīng)綁定過 Int.self 類型了,不需要再編譯檢查了。
    testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}
10
20
(lldb) 

這里能夠綁定成功是因為元組在內(nèi)存中也是連續(xù)存放數(shù)據(jù)的,這里的<Int,Int>元組實際上在內(nèi)存上就是連續(xù)8字節(jié)存儲的。應(yīng)用場景就是某些接口不兼容的時候需要轉(zhuǎn)換。

結(jié)構(gòu)體指針轉(zhuǎn)換

原則是要把第一個int屬性的地址傳過去。

struct HeapObject {
    var strongRef: Int
    var unownedRef: Int
}

func testPointer(_ p : UnsafePointer<Int>) {
    print(p.pointee)
}

var hp = HeapObject(strongRef: 10, unownedRef: 20)

withUnsafeMutablePointer(to: &hp) { (ptr : UnsafeMutablePointer<HeapObject>) in
    //1.這里通過取strongRef的地址,其實ptr和strongRefPtr是同一個。需要注意的一點是外層要改為withUnsafeMutablePointer,否則無法對 ptr.pointee.strongRef &
//    let strongRefPtr = withUnsafePointer(to: &ptr.pointee.strongRef){$0}
//    testPointer(strongRefPtr)
    //2.通過 advance 步長 advance 0,由于 ptr 步長 1 的話就跳過 hp 了,需要轉(zhuǎn)換為 UnsafeRawPointer 然后 advanced
//    let strongRefPtr = UnsafeRawPointer(ptr).advanced(by: 0)
//    testPointer(strongRefPtr.assumingMemoryBound(to: Int.self))
    
    //3. ptr 的地址 + 偏移量,這里可以直接傳ptr。因為HeapObject結(jié)構(gòu)體地址首個屬性就是strongRef
    //offset 的參數(shù)是keypath,如果path改為 unownedRef,那輸出的就是unownedRef的值。
//    let strongRefPtr = UnsafeRawPointer(ptr) + MemoryLayout<HeapObject>.offset(of: \HeapObject.strongRef)!
//    testPointer(strongRefPtr.assumingMemoryBound(to: Int.self))//在這里等效4
   //4.直接傳ptr地址
    testPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))
}

withMemoryRebound

withMemoryRebound用于臨時更改指針類型,用于我們不希望修改指針類型的情況下。

var age = 10
func testPointer(_ value: UnsafePointer<UInt64>){
    print(value.pointee)
}

let ptr = withUnsafePointer(to: &age){$0}
//臨時修改ptr類型,只在閉包表達式是UnsafePointer<UInt64>,出了作用域仍然為UnsafePointer<Int>。
ptr.withMemoryRebound(to: UInt64.self, capacity: 1) { (ptr: UnsafePointer<UInt64>) in
    testPointer(ptr)
}

臨時修改ptr類型,只在閉包表達式是UnsafePointer<UInt64>,出了作用域仍然為UnsafePointer<Int>。

  • withMemoryRebound:臨時更改內(nèi)存綁定類型。
  • bindMemory<T>(to type: T.Type, capacity count: Int):更改內(nèi)存綁定的類型,如果之前沒有綁定,那么就是首次綁定;如果綁定過了,會被重新綁定為該類型。
  • assumingMemoryBound:假定內(nèi)存綁定,這里是告訴編譯器已經(jīng)綁定過類型了,不需要再編譯檢查了。

內(nèi)存操作都是不安全的,需要我們自己管理負責(zé)。

參考:
http://www.lxweimin.com/p/358f0cd7d823

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內(nèi)容