iOS內(nèi)存分區(qū)
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
中
(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è)變量的地址,可以看到age
和age2
離額比較近戈毒。這也就驗(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è)值碳蛋。
那么去掉
static
(lldb) po &age3
0x0000000100003fa0
(lldb) cat address 0x0000000100003fa0
&0x0000000100003fa0, age3 <+0> CTest.__TEXT.__const
發(fā)現(xiàn)age3
在__TEXT.__const
再驗(yàn)證下
*p
。__text
就是我們當(dāng)前執(zhí)行的指令省咨。
在swift
中肃弟,調(diào)試的時(shí)候全局變量初始化前在__common
段,初始化后還是在__common
段零蓉,按理說應(yīng)該在__data
段的笤受。
總結(jié):分區(qū)(人為的籠統(tǒng)的劃分)和 macho
文件不是一個(gè)東西(放在segment
的section
中,符號(hào)劃分更細(xì))敌蜂÷崾蓿可以簡單理解為Windows
和 MacOS
方法調(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)試
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)
-
hotpot地址偏移x60然后跳轉(zhuǎn)微渠。x60就是test2
image.png
可以看到test、test1咧擂、test2逞盆、test3是連續(xù)的地址,這里也就驗(yàn)證了class中方法是連續(xù)存放的松申。
源碼觀察
搜索initClassVTable
并打個(gè)斷點(diǎn)
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")
}
}
可以看出變成了直接調(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文件.
可以看到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ì)"覆蓋"同名方法的原因。
- 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)換文件
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è)過程究竟做了什么處理
@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()
可以看到依然是函數(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ì)算屬性墩朦。而要更改屬性的行為也就是更改set
和get
方法就好了。所以這接給擴(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")
}
}
可以看到調(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
源碼
Builtin
Swift標(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)管理番川,
allocate
與deallocate
匹配,initialize
與deinitialize
匹配脊框。
應(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)
這里t
和ptr
的區(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é)骚秦。