Swift 对象内存模型探究(一)
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/zIkB9KnAt1YPWGOOwyqY3Q 作者:王振宇
内存分配
MemoryLayout基本使用方法
MemoryLayout<Int>.size //8
let a: Int = 10
MemoryLayout.size(ofValue: a) //8
MemoryLayout 属性介绍
alignment & alignment(ofValue: T)这个属性是与内存对齐相关的属性。许多计算机系统对基本数据类型的合法地址做出了一些限制,要求某种数据类型对象的地址必须是某个值 K(通常是 2、4或者8)的倍数。这种对齐限制简化了形成处理器和内存系统之间接口的硬件设计。对齐原则是任何 K 字节的基本对象的地址必须是 K 的倍数。 MemoryLayout size & size(ofValue: T)一个 T 数据类型实例占用连续内存字节的大小。 stride & stride(ofValue: T)在一个 T 类型的数组中,其中任意一个元素从开始地址到结束地址所占用的连续内存字节的大小就是 注释:数组中有四个 T 类型元素,虽然每个 T 元素的大小为 基本数据类型的 MemoryLayout//值类型
MemoryLayout<Int>.size //8
MemoryLayout<Int>.alignment //8
MemoryLayout<Int>.stride //8
MemoryLayout<String>.size //24
MemoryLayout<String>.alignment //8
MemoryLayout<String>.stride //24
//引用类型 T
MemoryLayout<T>.size //8
MemoryLayout<T>.alignment //8
MemoryLayout<T>.stride //8
//指针类型
MemoryLayout<unsafeMutablePointer<T>>.size //8
MemoryLayout<unsafeMutablePointer<T>>.alignment //8
MemoryLayout<unsafeMutablePointer<T>>.stride //8
MemoryLayout<unsafeMutableBufferPointer<T>>.size //16
MemoryLayout<unsafeMutableBufferPointer<T>>.alignment //16
MemoryLayout<unsafeMutableBufferPointer<T>>.stride //16
Swift 指针常用 Swift 指针类型在本文中主要涉及到几种指针的使用,在此简单类比介绍一下。
Swift 获取指向对象的指针final func withUnsafeMutablePointers<R>(_ body: (UnsafeMutablePointer<Header>,UnsafeMutablePointer<Element>) throws -> R) rethrows -> R
//基本数据类型
var a: T = T()
var aPointer = a.withUnsafeMutablePointer{ return $0 }
//获取 struct 类型实例的指针,From HandyJSON
func headPointerOfStruct() -> UnsafeMutablePointer<Int8> {
return withUnsafeMutablePointer(to: &self) {
return UnsafeMutableRawPointer($0).bindMemory(to: Int8.self,capacity: MemoryLayout<Self>.stride)
}
}
//获取 class 类型实例的指针,From HandyJSON
func headPointerOfClass() -> UnsafeMutablePointer<Int8> {
let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self,capacity: MemoryLayout<Self>.stride)
return UnsafeMutablePointer<Int8>(mutableTypedPointer)
}
Struct 内存模型在 Swift 中,struct 是值类型,一个没有引用类型的 Struct 临时变量都是在栈上存储的: struct Point {
var a: Double
var b: Double
}
MemoryLayout<Point>.size //16
内存模型如图: 再看另一种情况: struct Point {
var a: Double?
var b: Double
}
MemoryLayout<Point>.size //24
可以看到,如果将属性 MemoryLayout<Double>.size //8
MemoryLayout<Optional<Double>>.size //9
之所以 由于 所以,从以上例子可以得出一个结论:Swift 的可选类型是非常浪费内存空间的。 操作内存修改一个 Struct 类型实例的属性的值struct Demo下面展示了一个简单的结构体,我们将用这个结构体来完成一个示例操作: enum Kind {
case wolf
case fox
case dog
case sheep
}
struct Animal {
private var a: Int = 1 //8 byte
var b: String = "animal" //24 byte
var c: Kind = .wolf //1 byte
var d: String? //25 byte
var e: Int8 = 8 //1 byte
//返回指向 Animal 实例头部的指针
func headPointerOfStruct() -> UnsafeMutablePointer<Int8> {
return withUnsafeMutablePointer(to: &self) {
return UnsafeMutableRawPointer($0).bindMemory(to: Int8.self,capacity: MemoryLayout<Self>.stride)
}
func printA() {
print("Animal a:(a)")
}
}
操作首选我们需要初始化一个 let animal = Animal() // a: 1,b: "animal",c: .wolf,d: nil,e: 8
拿到指向 let animalPtr: unsafeMutablePointer<Int8> = animal.headPointerOfStruct() 现在内存中的情况如图所示: PS: 由图可以看到 如果我们想要通过内存修改 //将之前得到的指向 animal 实例的指针转化为 rawPointer 指针类型,方便我们进行指针偏移操作
let animalRawPtr = unsafeMutableRawPointer(animalPtr)
let intValueFromJson = 100
let aPtr = animalRawPtr.advance(by: 0).assumingMemoryBound(to: Int.self)
aPtr.pointee // 1
animal.printA() //Animal a: 1
aPtr.initialize(to: intValueFromJson)
aPtr.pointee // 100
animal.printA() //Animal a:100
通过以上操作,我们成功把 代码分析首先, 所以,我们先将 animalPtr 转换为
默认某块内存区域已经绑定了某种数据类型(在本例中如图绿色的内存区域是 所以,通过 在 Swift 中指针有一个叫做 因为 之后,我们使用 修改后面属性值的思路都是一样的,首先通过对 Class 内存模型
class Human {
var age: Int?
var name: String?
var nicknames: [String] = [String]()
//返回指向 Human 实例头部的指针
func headPointerOfClass() -> UnsafeMutablePointer<Int8> {
let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self,capacity: MemoryLayout<Human>.stride)
return UnsafeMutablePointer<Int8>(mutableTypedPointer)
}
}
MemoryLayout<Human>.size //8
Human 类内存分布如图: 类型信息区域在 32bit 的机子上是 4byte,在 64bit 机子上是 8 byte。引用计数占用 8 byte。所以,在堆上,类属性的地址是从第 16 个字节开始的。 操作内存修改一个 Class 类型实例属性的值与修改 let human = Human()
let arrFormJson = ["goudan","zhaosi","wangwu"]
//拿到指向 human 堆内存的 void * 指针
let humanRawPtr = unsafeMutableRawPointer(human.headerPointerOfClass())
//nicknames 数组在内存中偏移 64byte 的位置(16 + 16 + 32)
let humanNickNamesPtr = humanRawPtr.advance(by: 64).assumingMemoryBound(to: Array<String>.self)
human.nicknames
//[]
humanNickNamePtr.initialize(arrFormJson)
human.nicknames //["goudan","zhaosi","wangwu"]
玩一玩 Class 类型中的数组属性如 在 C 中,指向数组的指针其实是指向数组中的第一个元素的,比如假设 同理,在 Swift 中也是适用的。在本例中, let firstElementPtr = humanRawPtr.advance(by: 64).assumingMemoryBound(to: unsafeMutablePointer<String>.self).pointee
如图: 所以,在理论上,我么就可以用 在 Playground 上运行后并没有像我们的预期一样显示出 “goudan”,难道我们的理论不对吗,这不科学!本着打破砂锅问到底,问题解决不了就睡不着觉的精神,果然摸索出了一点规律: 通过直接获取到原数组 可以看到, 所以,通过我们的方式获取到的 PS: 虽然原因搞明白了,但是数组开头的那 32 个字节博主至今没搞明白是做啥用的,有了解的童鞋可以告知一下博主。 所以,我们需要做的就是将 Class Type 之挂羊头卖狗肉Type 的作用先假设如下代码: class Drawable {
func draw() {
}
}
class Point: Drawable {
var x: Double = 1
var y: Double = 1
func draw() {
print("Point")
}
}
class Line: Drawable {
var x1: Double = 1
var y1: Double = 1
var x2: Double = 2
var y2: Double = 2
func draw() {
print("Line")
}
}
var arr: [Drawable] = [Point(),Line()]
for d in arr {
d.draw() //问题来了,Swift 是如何判断该调用哪一个方法的呢?
}
在 Swift 中,class 类型的方法派发是通过 V-Table 来实现动态派发的。Swift 会为每一种类类型生成一个 Type 信息并放在静态内存区域中,而每个类类型实例的 type 指针就指向静态内存区域中本类型的 Type 信息。当某个类实例调用方法的时候,首先会通过该实例的 type 指针找到该类型的 Type 信息,然后通过信息中的 V-Table 得到方法的地址,并跳转到相应的方法的实现地址去执行方法。 替换一下 Type 会怎样通过上面的分析,我们知道一个类类型的方法派发是通过头部的 type 指针来决定的,如果我们将某个类实例的 type 指针指向另一个 type 会不会有什么好玩的事情发生呢?哈哈 ~ 一起来试试 ~ class Wolf {
var name: String = "wolf"
func soul() {
print("my soul is wolf")
}
func headPointerOfClass() -> UnsafeMutablePointer<Int8> {
let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self,capacity: MemoryLayout<Wolf>.stride)
return UnsafeMutablePointer<Int8>(mutableTypedPointer)
}
}
class Fox {
var name: String = "fox"
func soul() {
print("my soul is fox")
}
func headPointerOfClass() -> UnsafeMutablePointer<Int8> {
let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self,capacity: MemoryLayout<Fox>.stride)
return UnsafeMutablePointer<Int8>(mutableTypedPointer)
}
}
可以看到以上 let wolf = Wolf()
var wolfPtr = UnsafeMutableRawPointer(wolf.headPointerOfClass())
let fox = Fox()
var foxPtr = UnsafeMutableRawPointer(fox.headPointerOfClass())
foxPtr.advanced(by: 0).bindMemory(to: UnsafeMutablePointer<Wolf.Type>.self,capacity: 1).initialize(to: wolfPtr.advanced(by: 0).assumingMemoryBound(to: UnsafeMutablePointer<Wolf.Type>.self).pointee)
print(type(of: fox)) //Wolf
fox.name //"fox"
fox.soul() //my soul is wolf
神奇的事情发生了,一个 Fox 类型的实例竟然调用了 Wolf 类型的方法,哈哈 ~ 如果还有什么好玩的玩法,大家可以继续探究 ~ 参考文章Swift进阶之内存模型和方法调度 更多精彩内容欢迎关注腾讯 Bugly的微信公众账号: 腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS,Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |