内存分区
我们可以通过插件libfooplugin.dylib 查看当前地址处于什么区 ,密码: vpha
栈区
栈区存放局部变量和函数运行时的上下文
func test(){
var age: Int = 10
print(age)
}
test()
查看SIL
通过Swift Intermediate Language (SIL)查找alloc_stack
注意是局部变量,如果使用let修饰的话
func test(){
let age: Int = 10
print(age)
}
test()
查看SIL
堆区
堆区是通过new & malloc分配空间,不连续,类似链表
class JClass {
}
func test(){
var t = JClass()
print(t)
}
test()
全局区
#include <stdio.h>
int age;
int a = 10;
int main(int argc, const char * argv[]) {
printf(a);
return 0;
}
多验证几个
#include <stdio.h>
int age;
int a = 10;
int age1;
int b = 20;
int main(int argc, const char * argv[]) {
printf(a);
return 0;
}
那么数据模型对全局区进一步划分
static
全局已经初始化静态变量未使用时
#include <stdio.h>
static int age = 10;
int main(int argc, const char * argv[]) {
printf("hello");
return 0;
}
那么我们使用一下
#include <stdio.h>
int a = 10;
int b;
int c = 20;
int d;
static int age = 10;
static int age2;
int main(int argc, const char * argv[]) {
printf("%d\n",age);
printf("%d\n",age2);
printf("end");
return 0;
}
通过MachOView查看
static修饰的全局变量只有使用的时候才会存储,这一点和普通的全局变量有区别
var
是不是应该在全局已初始化区__DATA,__data??
var a = 18
var b = a
print("end")
并不是
__DATA,__data而是__DATA,__common!!!是在全局未初始化区!!!
let
这个是不是应该在全局已初始化区__DATA,__data??
var a = 18
var b = a
print("end")
查找不到符号
通过汇编查看
0x100003e50 + 0x41d8 = 0x100008028
也是__DATA,__common!!!是在全局未初始化区!!!
猜测:swift中好多地方都用到了延迟加载,这里应该也是因为编译的时候延迟加载的缘故,在编译的时候定义符号但并没有赋值,所以当作全局未初始化的变量处理,存储在__DATA,__common中,在运行时第一次使用到才会被赋值但不会改变地址
这里只是猜测,swift中的具体实现机制还没有搞明白,留下疑问
常量区
使用const关键字修饰
#include <stdio.h>
int a = 10;
const int age3 = 20;
int main(int argc, const char * argv[]) {
printf("end");
return 0;
}
如果同时使用static const修饰呢
#include <stdio.h>
int a = 10;
static const int age3 = 20;
int main(int argc, const char * argv[]) {
printf("end");
return 0;
}
通过汇编验证
代码段(⚠️这里只是我的个人理解⚠️)
通过MachOView来查看
__TEXT,__test
存储编译之后生成的汇编指令
__TEXT,__stubs
存储运行过程中需要动态绑定的一些函数,例如print函数只有运行的时候才知道我要打印谁,汇编指令
__TEXT,__stubs_helper
存储运行过程中需要动态绑定的一些汇编指令
__TEXT,__cstring
代码中用到的字符串符号
__TEXT,__unwind_info
不知道干啥的,但是看起来好像是记录内存分配的长度
Symbol Table
符号表,根据命名重整之后的符号来查找对应的符号,进而确定函数地址等信息,注意,如果在编译器就能确定的函数地址那么就不需要到这里来查询,releas包中的符号表也会少很多,只剩下不能在编译器确定的那些符号
Dynamic Symbol Table
动态符号表,存储运行时才能动态确定的一些符号
String Table
字符串表,存储代码中的字符串们
方法调度V-Table
首先记录几个ARM64架构的汇编指令
blr 带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址
mov 将某一寄存器的值复制到另一寄存器中(只能用于寄存器和寄存器,或者寄存器和常量之间传值,不能用于内存地址),如:mov: x1, x0表示将x0的值复制到x1寄存器中
ldr 将内存中的值读取到寄存器中,如 ldr x0, [x1, x2]表示将寄存器x1和x2的地址相加作为地址,取地址中的值赋值给x0
str 将寄存器中的值写入内存中,如 str x0, [x0 ,x1]表示将x0的值保存到x0+x1处
bl 跳转到某地址
读取内存起地址的lldb指令 register read x8
对于结构体中的方法都是静态调用(直接调用),而在swift中是通过V-Table进行调度的,V-Table在SIL中是这么定义的
decl ::= sil-vtable
sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-name
V-Table类似数组结构,声明在class内部的方法在不加任何关键字修饰的情况下连续存放在V-Table所在地址空间中
class JClass {
func a() {}
func b() {}
func c() {}
func d() {}
}
class Sub: JClass {
override func a() {}
func e() {}
}
查看SIL
通过源码可以看到V-Table是通过for循环的形式存储在一块连续的内存空间中,具体的流程在后面类结构探索时详细研究
extension
在类扩展中声明的函数是直接调用的,这一点和OC有区别,并没有将扩展中的函数插入原有类的函数表中,OC的函数表是二维数组,可以知道类中的函数起始位置,但是我们在SIL文件中看到swift中的函数表V-Table是一维数组,并没有记录父类函数的位置
final
final修饰的函数也是静态调用的
@objc+NSObject
swift不能直接给OC使用
class JClass {
func a() {}
func b() {}
func c() {}
func d() {}
}
var t = JClass()
t.a()
只有一些宏定义,没有发现任何类相关的声明,这个时候在OC中也访问不到JClass类
让类继承自NSObject
class JClass:NSObject {
func a() {}
func b() {}
func c() {}
func d() {}
}
var t = JClass()
t.a()
这时发现了暴露给OC的类声明
@objc标记的函数可以暴露给OC使用
class JClass:NSObject {
@objc func a() {}
func b() {}
func c() {}
func d() {}
}
var t = JClass()
t.a()
有了暴露给OC使用的方法,这时就可以在OC中访问a方法
通过SIL查看可以知道暴露给OC的函数默认还是调用了swift的函数,另外加上了retain和release
要想在OC中使用swift中的方法需要同时满足类继承自NSObject和@objc标记方法两个条件
dynamic
dynamic修饰的函数具有动态的特性,可以使用@_dynamicReplacement(for:)动态修改方法实现
class JClass {
dynamic func a() {print("a")}
}
extension JClass{
@_dynamicReplacement(for:a)
func b() {print("b")}
}
var t = JClass()
t.a()
如果方法a不存在,那么编译器会报错
如果方法a存在但是没有标记dynamic编译器也会报错
dynamic+@objc+NSObject
class JClass {
@objc dynamic func a() {}
func b() {}
func c() {}
func d() {}
}
var t = JClass()
t.a()
既暴露给OC使用又有动态特性,那就直接成了动态消息转发objc-msgSend
这时也就可以进行Method Swizzling操作
class JClass:NSObject {
@objc dynamic func a() {}
func b() {}
func c() {}
func d() {}
}
var t = JClass()
t.a()
待验证
小结
- 类扩展
extension中的方法是静态调用 - 任何给
OC使用的swift类都必须继承自NSObject final标记的函数会变成直接调用,不能被继承dynamic标记的函数有了动态特性,没有改变调用方式@objc标记的函数暴露给OC使用,没有改变调用方式dynamic+@objc标记的函数既有动态性又暴露给OC,调用方式变成动态消息转发,可以进行Method Swizzling操作dynamic+@objc+NSObject给OC使用的动态swift类
指针的简单介绍
swift中的指针分为两类,指定类型指针typed pointer和原生指针raw pointer
typed pointer在Swift中的表示是UnsafePoinster<T>
raw pointer在Swift中的表示是UnsafeRawPointer
Swift和OC的对应关系
| Swift | OC | 说明 |
|---|---|---|
| UnsafePoinster< T > | const T * | 指针不可变 |
| UnsafeMutablePoinster | T * | 指针可变 |
| UnsafeRawPointer | const void * | 指针指向未知类型 |
| UnsafeMutableRawPointer | void * | 指针指向未知类型 |
raw pointer的使用
我们要开辟空间来存储4个Int类型的值,怎么实现呢
//开辟32字节空间,以8字节对齐
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
//advanced表示指针前进的步长,这里我们取字节对齐的整数倍 MemoryLayout.stride
//storeBytes表示数据存储,需要知道类型
for i in 0..<4{
p.advanced(by: i*8).storeBytes(of: i+1, as: Int.self)
}
//load用来读取数据,fromByteOffset相对首地址的指针偏移
for i in 0..<4{
let a = p.load(fromByteOffset: i*8, as: Int.self)
print(a)
}
//释放
p.deallocate()
打印结果
Type pointer的使用
var age = 10;
print(age)
age = withUnsafePointer(to: &age){prt in
print(prt)
return prt.pointee + 12
}
print(age)
withUnsafePointer(to: &age){prt in
print(prt)
}
可以看到withUnsafePointer指针指向的内存的值可以修改
但是不能通过withUnsafePointer指针直接修改
var age = 10;
withUnsafePointer(to: &age){
$0.pointee+=12
}
这时会报错
withUnsafeMutablePointer指向的值可以改变,也可以通过withUnsafeMutablePointer指针直接修改值
var age = 10
print(age)
age = withUnsafeMutablePointer(to: &age){
print($0)
return $0.pointee + 12
}
print(age)
withUnsafeMutablePointer(to: &age){
print($0)
$0.pointee += 12
}
print(age)
可以看到地址没有改变,值修改了
另一种开辟空间的方法
//另一种开辟空间的做法
var age = 10
let p = UnsafeMutablePointer<Int>.allocate(capacity: 4)
//通过advanced移动指针赋值和访问
for i in 0..<4{
p.advanced(by: i).initialize(to: i+1)
}
for i in 0..<4{
let value = p.advanced(by: i).pointee
print(value)
}
//通过successor+predecessor赋值和访问
//successor表示后一个 predecessor表示前一个
var p1 = p;
for i in 0..<4{
p1.initialize(to: i+1)
p1 = p1.successor()
}
var p2 = p;
for i in 0..<4{
let value = p2.pointee
p2 = p2.successor()
print(value)
}
//还可以这样赋值和访问
for i in 0..<4{
(p+i).initialize(to: i+1)
}
for i in 0..<4{
let value = (p+i).pointee
print(value)
}
//也可以通过这种方式访问
for i in 0..<4{
p[i] = i+1
}
for i in 0..<4{
let value = p[i]
print(value)
}
//注意使用完之后一定要释放
p.deinitialize(count: 4)
p.deallocate()
这几种方式都可以正常的赋值和访问
以上是值类型,如果是引用类型呢
class JClass {
var age:Int = 18
var name:String = "SC"
}
var p = UnsafeMutablePointer<JClass>.allocate(capacity: 2)
var j = JClass()
var j1 = JClass()
p.initialize(to: j)
p.advanced(by: 1).initialize(to: j1)
withUnsafePointer(to: &j){$0.pointee.age = 20}
在withUnsafePointer的$0.pointee中存储的是实例对象地址,只要地址不改变就可以,是可以通过地址来修改属性的,但是如果想要替换$0.pointee的值就报错了
class JClass {
var age:Int = 18
var name:String = "SC"
}
var p = UnsafeMutablePointer<JClass>.allocate(capacity: 2)
var j = JClass()
var j1 = JClass()
p.initialize(to: j)
p.advanced(by: 1).initialize(to: j1)
withUnsafePointer(to: &j){$0.pointee = j1}
这时使用withUnsafeMutablePointer可以实现
class JClass {
var age:Int = 18
var name:String = "SC"
}
var p = UnsafeMutablePointer<JClass>.allocate(capacity: 2)
var j = JClass()
var j1 = JClass()
p.initialize(to: j)
p.advanced(by: 1).initialize(to: j1)
withUnsafeMutablePointer(to: &j){$0.pointee = j1}
raw pointer和type pointer使用过程中注意步长advanced的区别,原生指针中步长以字节为单位,type pointer是以具体类型占用内存长度为单位
Unmanaged(非托管)
下面代码打印结果是什么??应该是打印实例变量t的地址
class JClass {
var age:Int = 18
var name:String = "SC"
}
var t = JClass()
var p = withUnsafePointer(to: &t){ $0 }
print(p.pointee)
不符合预期,直接打印了类名称
那么这个时候我们就需要借助Unmanaged来获取类的实例对象指针,Unmanaged类似于__bridge,所有权的转换
class JClass {
var age:Int = 18
var name:String = "SC"
}
var t = JClass()
var p = withUnsafePointer(to: &t){ $0 }
var p1 = Unmanaged.passUnretained(t as AnyObject).toOpaque()
print(p1)
print("end")
bindMemory内存绑定
struct HeapObject {
var kind:UnsafePointer<Int>
var strongref: UInt32
var unownedref: UInt32
}
class JClass {
var age:Int = 18
var name:String = "SC"
}
var t = JClass()
let p1 = Unmanaged.passUnretained(t as AnyObject).toOpaque()
let p2 = p1.bindMemory(to: HeapObject.self, capacity: 1)
print(p2.pointee)
这时原本指向JClass实例对象的地址就指向了HeapObject的类型对象,之所以可以这样绑定是因为内存结构是一样的,class的第一个字段是metadata8字节,第二个字段是countRef8字节,我们拆分成了两个4字节
那如果内存结构对应不上会怎么样呢
struct HeapObject {
var kind:String
var strongref: UInt32
var unownedref: UInt32
}
class JClass {
var age:Int = 18
var name:String = "SC"
}
var t = JClass()
let p1 = Unmanaged.passUnretained(t as AnyObject).toOpaque()
let p2 = p1.bindMemory(to: HeapObject.self, capacity: 1)
print(p2.pointee)
这时就报错了
assumingMemoryBound假定内存绑定
var a = (4,8)
func f(c:UnsafePointer<Int>){
}
withUnsafePointer(to: &a) { (ptr:UnsafePointer<(Int,Int)>) in
f(c: ptr)
}
函数f接收的是一个UnsafePointer<Int>类型的指针,但是传过去的是一个UnsafePointer<(Int,Int)>,类型不匹配报错
但是我们知道元组类型的指针其实就是指向第一个元素的指针,也就是UnsafePointer<Int>类型的,那么我们可以使用假定内存绑定指针assumingMemoryBound(to: )来告诉编译器我就是UnsafePointer<Int>类型的,你不需要来检查我了
var a = (4,8)
func f(c:UnsafePointer<Int>){
}
withUnsafePointer(to: &a) { (ptr:UnsafePointer<(Int,Int)>) in
f(c: UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))
}
这个时候是可以编译通过的!!!
从前面的表格我们看到UnsafePointer<T>相当于OC中的const T *,指针指向的位置不可变
var b = 1
struct S {
var f:Int
var f1:Int
var f2:String
}
let p = withUnsafePointer(to: &b) { (ptr:UnsafePointer<Int>) in
print(ptr)
UnsafeRawPointer(ptr).bindMemory(to: S.self, capacity: 1)
print(ptr)
}
print("end")
我们看到执行bindMemory前后并没有改变指针的指向,只是认为指向内存的类型发生了变化
所以一定要在能确定内存布局一致的情况下使用bindMemory和assumingMemoryBound,否则就可能报错