Swift底層探索(三):指針

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

image.png

Stack

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

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)

直接定義一個(gè)局部變量,然后lldb查看一下盒发,可以看到是一個(gè)Stack address兔沃。

Heap

對(duì)于堆空間,通過new & malloc關(guān)鍵字來申請(qǐng)內(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中史汗。這里將未初始化和已初始化變量分開存放(劃分更細(xì))琼掠,便于定位符號(hào)。

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

(lldb) cat address 0x0000000100008014

age2位于__DATA.__data中。
仔細(xì)觀察3個(gè)變量的地址,可以看到ageage2離額比較近戈毒。這也就驗(yàn)證了全局區(qū)分開存儲(chǔ)了已初始化和未初始化比變量艰猬。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) 

接著上面的例子恐疲,定義一個(gè)static const修飾的age3,發(fā)現(xiàn)查找不到地址腊满,斷點(diǎn)調(diào)試發(fā)現(xiàn)直接賦值的是30。也就是沒有age3這個(gè)符號(hào)培己,直接存儲(chǔ)的是30這個(gè)值碳蛋。

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

再驗(yàn)證下*p
image.png

__text

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

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

總結(jié):分區(qū)(人為的籠統(tǒng)的劃分)和 macho文件不是一個(gè)東西(放在segmentsection中,符號(hào)劃分更細(xì))敌蜂÷崾蓿可以簡單理解為WindowsMacOS

方法調(diào)度

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

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

V-table是一個(gè)數(shù)組結(jié)構(gòu)擴(kuò)展中是直接調(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)識(shí)就是類落包, sil-decl-ref聲明部蛇,sil-function-name方法名稱。
直接看一個(gè)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)部的方法時(shí)不加任何關(guān)鍵字修飾的時(shí)候就連續(xù)存放在我們當(dāng)前的地址空間中有序。

斷點(diǎn)觀察

斷點(diǎn)直觀觀察一下抹腿,先熟悉一下匯編指令:

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ǎ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ù)的地址,這里也就驗(yàn)證了class中方法是連續(xù)存放的松申。

源碼觀察

搜索initClassVTable并打個(gè)斷點(diǎn)

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());
    }
  }

從源碼中可以看到直接是一個(gè)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有可能放在任意一個(gè)文件中水醋,那編譯的時(shí)候只有編譯到的時(shí)候才知道有extension旗笔。此時(shí)擴(kuò)展中的方法可以插入到父類,但無法插入到子類V-Table中拄踪。因?yàn)闆]有指針記錄哪塊是父類方法蝇恶,哪塊是子類方法,并且繼承層級(jí)可能很多惶桐,加指針記錄也不現(xiàn)實(shí)撮弧。(OC是通過移動(dòng)內(nèi)存地址合成的)的猛。

OC方法列表合成

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


image.png
  • V-Table是一個(gè)數(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>動(dòng)態(tài)調(diào)用

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

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

final

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

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

@objc

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

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

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

編譯器會(huì)自己生成轉(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文件查看一下這個(gè)過程究竟做了什么處理


image.png

@objc標(biāo)記的方法生成了兩個(gè)方法,一個(gè)是swift原有的方法啼染,另一個(gè)是生成供oc調(diào)用的方法宴合,oc調(diào)用的這個(gè)方法內(nèi)部調(diào)用了swift的方法
更詳細(xì)資料: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的動(dòng)態(tài)轉(zhuǎn)發(fā)功能迹鹅;
  • dynamic只用于類卦洽,不能用于結(jié)構(gòu)體和枚舉,因?yàn)樗鼈儧]有繼承機(jī)制徒欣,而Objc的動(dòng)態(tài)轉(zhuǎn)發(fā)就是根據(jù)繼承關(guān)系來實(shí)現(xiàn)轉(zhuǎn)發(fā)逐样。
  • Swift 中我們也是可以使用 KVO 的,但是僅限于在 NSObject 的子類中打肝。這是可以理解的脂新,因?yàn)?KVO 是基于 KVC (Key-Value Coding) 以及動(dòng)態(tài)派發(fā)技術(shù)實(shí)現(xiàn)的,而這些東西都是 Objective-C 運(yùn)行時(shí)的概念粗梭。另外由于 Swift 為了效率争便,默認(rèn)禁用了動(dòng)態(tài)派發(fā),因此想用 Swift 來實(shí)現(xiàn) KVO断医,我們還需要做額外的工作滞乙,那就是將想要觀測的對(duì)象標(biāo)記為 dynamic奏纪。
  • OC中@dynamic關(guān)鍵字告訴編譯器不要為屬性合成getter和setter方法。
  • Swift中dynamic關(guān)鍵字斩启,可以用于修飾變量或函數(shù)序调,意思與OC完全不同。它告訴編譯器使用動(dòng)態(tài)分發(fā)而不是靜態(tài)分發(fā)兔簇。
  • 標(biāo)記為dynamic的變量/函數(shù)會(huì)隱式的加上@objc關(guān)鍵字发绢,它會(huì)使用OC的runtime機(jī)制。
    https://www.cnblogs.com/feng9exe/p/9084788.html
    http://www.reibang.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實(shí)際上調(diào)用到了test1垄琐。

既然可以替換方法边酒,那么能不能替換屬性呢?
首先要清楚extension中不能添加存儲(chǔ)屬性狸窘。只能添加計(jì)算屬性墩朦。而要更改屬性的行為也就是更改setget方法就好了。所以這接給擴(kuò)展添加一個(gè)存儲(chǔ)屬性并綁定到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)變成了動(dòng)態(tài)轉(zhuǎn)發(fā)翻擒。

指針

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

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

Swift指針與OC對(duì)應(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)存中存儲(chǔ)連續(xù)4個(gè)整形的數(shù)據(jù),用raw pointer來處理

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

for i in 0 ..< 4 {
    //advanced 步長春哨,相當(dāng)于p前進(jìn) i*8 存儲(chǔ) 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運(yùn)行時(shí)負(fù)擔(dān)恩伺。

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

typed pointer簡單用法
方式一

var age = 18

//p的值由閉包表達(dá)式返回值決定
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 對(duì)應(yīng),allocate 與 deallocate 對(duì)應(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是因?yàn)檫@里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ì)目的往前移動(dòng)等效(ptr + 1).pointee
print(ptr.successor().pointee)

ptr.deinitialize(count: 2)
ptr.deallocate()
  • 對(duì)指針的內(nèi)存管理需要我們手動(dòng)管理番川,allocatedeallocate匹配,initializedeinitialize 匹配脊框。

應(yīng)用

HeapObject(實(shí)例對(duì)象)結(jié)構(gòu)

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

class HPTeacher {
    var age = 18
}

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

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

//這里拿到的就是實(shí)例對(duì)象的值颁督。因?yàn)镽aw pointer  和 type pointer都需要我們自己管理內(nèi)存。 Unmanaged可以托管指針浇雹。
//passUnretained 這里有點(diǎn)類似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昭灵,如果綁定過了會(huì)重新綁定吠裆。
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(實(shí)例對(duì)象)
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) 

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

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

原則是要把第一個(gè)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的地址敌完,其實(shí)ptr和strongRefPtr是同一個(gè)储耐。需要注意的一點(diǎn)是外層要改為withUnsafeMutablePointer,否則無法對(duì) 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。因?yàn)镠eapObject結(jié)構(gòu)體地址首個(gè)屬性就是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用于臨時(shí)更改指針類型脯颜,用于我們不希望修改指針類型的情況下哟旗。

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

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

臨時(shí)修改ptr類型,只在閉包表達(dá)式是UnsafePointer<UInt64>矾芙,出了作用域仍然為UnsafePointer<Int>舍沙。

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末她倘,一起剝皮案震驚了整個(gè)濱河市璧微,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硬梁,老刑警劉巖前硫,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異荧止,居然都是意外死亡屹电,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門跃巡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來危号,“玉大人,你說我怎么就攤上這事素邪⊥饬” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵兔朦,是天一觀的道長偷线。 經(jīng)常有香客問我,道長沽甥,這世上最難降的妖魔是什么声邦? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮摆舟,結(jié)果婚禮上亥曹,老公的妹妹穿的比我還像新娘。我一直安慰自己盏檐,他們只是感情好歇式,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胡野,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痕鳍。 梳的紋絲不亂的頭發(fā)上硫豆,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音笼呆,去河邊找鬼熊响。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诗赌,可吹牛的內(nèi)容都是我干的汗茄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼铭若,長吁一口氣:“原來是場噩夢啊……” “哼洪碳!你這毒婦竟也來了递览?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤瞳腌,失蹤者是張志新(化名)和其女友劉穎绞铃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫂侍,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡儿捧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挑宠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菲盾。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖各淀,靈堂內(nèi)的尸體忽然破棺而出懒鉴,到底是詐尸還是另有隱情,我是刑警寧澤揪阿,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布疗我,位于F島的核電站念秧,受9級(jí)特大地震影響屿岂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜日裙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一溺健、第九天 我趴在偏房一處隱蔽的房頂上張望麦牺。 院中可真熱鬧,春花似錦鞭缭、人聲如沸剖膳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吱晒。三九已至,卻和暖如春沦童,著一層夾襖步出監(jiān)牢的瞬間仑濒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工偷遗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墩瞳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓氏豌,卻偏偏與公主長得像喉酌,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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