1.类与结构体的异同
主要的相同点:
- 定义存储值的属性
- 定义方法
- 定义下标以使用下标语法提供对其值的访问(点语法访问值)
- 定义初始化器
- 使用extension来拓展功能
- 遵循协议来提供某种功能
主要的不同点:
- 类有继承特性,而结构体没有
- 类型转换使你能够在运行时检查和解释类实例的类型(Mirro)
- 类有析构函数来释放其分配的资源(deinit)
- 引用计数允许对一个类实例有多个引用
对于类与结构体我们区分的第一件事就是:
类是引用类型
。也就意味着一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体实例内存地址的引用。
这里借助2个指令来查看当前变量的内存结构
po : p和po的区别在于使用po只会输出对应的值,而p则会输出返回值的类型以及命令结果的引用名。
x/8g : 读取内存中的值(8g:8字节格式输出)
class LGTeacher{
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
var t = LGTeacher(age: 18, name: "Kody")
var t1 = t
print("end")
(lldb) po t
(lldb) po t1
(lldb) x/8g 0x10070e630
0x10070e630: 0x0000000100008180 0x0000000600000003
0x10070e640: 0x0000000000000012 0x0000000079646f4b
0x10070e650: 0xe400000000000000 0x000000000000005f
0x10070e660: 0x0000000000000000 0x0000000000000000
(lldb) po withUnsafePointer(to: &t, {print($0)})
0x0000000100008218
0 elements
(lldb) po withUnsafePointer(to: &t1, {print($0)})
0x0000000100008220
0 elements
(lldb)
withUnsafePointer : 获取变量的内存地址
t和t1刚好差了8个字节,其实这8个字节存放的就是实例对象的内存地址
对于x/8g 0x100506400结果的解释
第一个8字节0x0000000100008180,毫无疑问,metadata(类似于OC中的isa)
第二个8字节0x0000000600000003,这里我还不是很清楚,1个存6一个存3,待后续了解后补充
第三个8字节0x0000000000000012,很明显低位的4字节存了18(age这个字段)
第四个8字节0x0000000079646f4b,低位的4字节存放了"Kody"所对应的ASCII。
iOS为小端模式,从右边开始读。0x4b-75-K;0x6f-111-o ;0x64-100-d;0x79-121-y
swift中有引用类型,就有值类型,最典型的就是
Struct
,结构体的定义也非常简单,相比较类类型的变量中存储的是地址,那么值类型存储就是具体的实例(或者说具体的值)
struct LGStudent{
var age: Int
var name: String
}
var s = LGStudent(age:18, name:"kody")
var s1 = s
print("end")
(lldb) po s
▿ LGStudent
- age : 18
- name : "kody"
(lldb) po s1
▿ LGStudent
- age : 18
- name : "kody"
(lldb)
其实引用类型就相当于在线的Excel,当我们把这个链接共享给别人的时候,别人的修改我们是能够看到的;值类型就相当于本地的Excel,当我们把本地的Excel传递给别人的时候,就相当于重新复制了一份给别人,因此他们对内容的修改我们是无法感知的。
另外引用类型和值类型还有一个最直观的区别就是存储的位置不同:一般情况,值类型存储在栈上,引用类型在堆上
。
首先我们对内存区域来一个基本概念的认知,大家看下面这张图
栈区(stack):局部变量和函数运行过程中的上下文
//test是不是一个函数
func test() {
//我们在函数内部声明的age变量是不是就是一个局部变量
var age: Int = 10
print(age)
}
test()
(lldb) po withUnsafePointer(to: &age, {print($0)})
0x00007ffeefbff468
0 elements
(lldb) cat address 0x00007ffeefbff468
address:0x00007ffeefbff468, stack address (SP: 0x7ffeefbff440 FP: 0x7ffeefbff470) swiftTest.test() -> ()
(lldb)
堆区(Heap):存储所有对象
全局区(Global):存储全局变量;常量;代码区
Segment&Section:Mach-o
文件有多个段(Segement),每个段有不同的功能,然后每个段又分为很多小的Section
TEXT.text : 机器码
TEXT.cString : 硬编码的字符串
TEXT.const : 初始化过的常量
DATA.data : 初始化过的可变的(静态/全局)变量
DATA.const : 没有初始化过的常量
DATA.bss : 没有初始化的(静态/全局)变量
DATA.common : 没有初始化过的符号声明
int age = 10;
int a;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
char *p = "LG";
NSLog(@"Hello, World!");
}
return 0;
}
(lldb) po &age
0x0000000100008020
(lldb) cat address 0x0000000100008020
address:0x0000000100008020, 8age <+0> , External: NO ocTest.__DATA.__data +8
(lldb) po &a
0x0000000100008024
(lldb) cat address 0x0000000100008024
address:0x0000000100008024, 0a <+0> , External: NO ocTest.__DATA.__common +0
(lldb) cat address 0x00007ffeefbff498
address:0x00007ffeefbff498, stack address (SP: 0x7ffeefbff490 FP: 0x7ffeefbff4b0) main
(lldb) x/8g 0x00007ffeefbff498
0x7ffeefbff498: 0x0000000100003f9e 0x00007ffeefbff4d0
0x7ffeefbff4a8: 0x0000000000000001 0x00007ffeefbff4c0
0x7ffeefbff4b8: 0x00007fff203abf3d 0x0000000000000000
0x7ffeefbff4c8: 0x0000000000000001 0x00007ffeefbff6b8
(lldb) cat address 0x0000000100003f9e
address:0x0000000100003f9e, 0ocTest.__TEXT.__cstring +0
(lldb)
我们来看例子
struct LGTeacher{
var age = 18
var name = "Kody"
}
func test(){
var t = LGTeacher()
print("end")
}
test()
frame varibale -L xxx
查看当前变量的地址
(lldb) frame variable -L t
0x00007ffeefbff450: (swiftTest.LGTeacher) t = {
0x00007ffeefbff450: age = 18
0x00007ffeefbff458: name = "Kody"
}
(lldb) x/8g 0x00007ffeefbff450
0x7ffeefbff450: 0x0000000000000012 0x0000000079646f4b
0x7ffeefbff460: 0xe400000000000000 0x0000000000000000
0x7ffeefbff470: 0x00007ffeefbff490 0x0000000100003644
0x7ffeefbff480: 0x00007ffeefbff4b0 0x0000000100015025
(lldb)
当运行至var t = LGTeacher()
,栈指针会向下移动24字节内存空间,然后把age和name拷贝到分配好的内存空间上。作用域完成后,栈指针移动还原,销毁栈内存
疑问:如果说当前结构体中有一个引用类型,会不会改变结构体的位置?
class LGPerson {
var age = 18
var name = "LGMan"
}
struct LGTeacher{
var age = 18
var name = "Kody"
var p = LGPerson()
}
func test(){
var t = LGTeacher()
print("end")
}
test()
(lldb) frame variable -L t
0x00007ffeefbff450: (swiftTest.LGTeacher) t = {
0x00007ffeefbff450: age = 18
0x00007ffeefbff458: name = "Kody"
scalar: p = 0x000000010076f140 {
0x000000010076f150: age = 18
0x000000010076f158: name = "LGMan"
}
}
(lldb) x/8g 0x00007ffeefbff450
0x7ffeefbff450: 0x0000000000000012 0x0000000079646f4b
0x7ffeefbff460: 0xe400000000000000 0x000000010076f140
0x7ffeefbff470: 0x00007ffeefbff490 0x0000000100002cb4
0x7ffeefbff480: 0x00007ffeefbff4b0 0x0000000100019025
我们可以发现,当结构体存在引用类型时,结构体依然存放在栈上,引用类型依然会在堆上创建,并且栈上存放的是引用类型的地址0x000000010076f140
,指向堆空间。
因此结构体中是否有引用类型并不影响结构体的存储位置。
当我们把struct改为class
class LGTeacher{
var age = 18
var name = "Kody"
}
func test(){
//栈上:分配8字节大小存储实例引用类型
//堆上:寻找合适的内存区域
//value的值拷贝到堆区内存空间中
//把栈上的内存地址指向当前堆区
var t = LGTeacher()
print("end")
}
test()
2.类与结构体的选择
- 1.引入github上StructVsClassPerformance这个案例来直观的测试当前结构体和类的时间分配
class Tests {
static func runTests() {
print("Running tests")
measure("class (1 field)") {
var x = IntClass(0)
for _ in 1...10000000 {
x = x + IntClass(1)
}
}
measure("struct (1 field)") {
var x = IntStruct(0)
for _ in 1...10000000 {
x = x + IntStruct(1)
}
}
measure("class (10 fields)") {
var x = Int10Class(0)
for _ in 1...10000000 {
x = x + Int10Class(1)
}
}
measure("struct (10 fields)") {
var x = Int10Struct(0)
for _ in 1...10000000 {
x = x + Int10Struct(1)
}
}
}
static private func measure(_ name: String, block: @escaping () -> ()) {
print()
print("\(name)")
let t0 = CACurrentMediaTime()
block()
let dt = CACurrentMediaTime() - t0
print("\(dt)")
}
}
统计1/10个变量的class和struct创建10000000次所需要的时间。
Running tests
class (1 field)
8.673463547999745
struct (1 field)
4.282427998999992
class (10 fields)
7.942918924999503
struct (10 fields)
4.6826105930003905
结果很明显,无论是1个变量还是10个变量,struct的性能都远高于class。
一般来说,尽可能优先选用结构体,结构体在栈上线程安全,在进行内存分配的过程中也是比堆区内存分配快得多
- 2.使用官方案例一
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [String : UIImage]()
func makeBalloon(_ color: Color, _ orientation: Orientation, _ tail: Tail) -> UIImage {
let key = "\(color):\(orientation):\(tail)"
if let image = cache[key] {
return image
}
...
}
分析:cache中使用string作为key值是有问题的。方法中的key(字符串)是存放在堆区上的,因此每次执行到该方法时,虽然有字典的缓存,缓存虽然命中,但是仍然会进行不停的堆内存的分配与销毁。很显然这个效率是无法接受的(此时该需求是聊天页面上的气泡)
那么我们应该使用struct作为cache的key值来提高效率
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [Balloon : UIImage]()
func makeBalloon(_ balloon: Balloon) -> UIImage {
if let image = cache[balloon] {
return image
}
...
}
struct Balloon: Hashable {
var color: Color
var orientation: Orientation
var tail: Tail
}
此时就没有了堆上的内存的分配与销毁
,对于我们当前的代码执行效率就非常高了
- 3.使用官方案例二
struct Attachment {
let fileURL: URL
let uuid: String
let mineType: String
init?(fileURL: URL, uuid: String, mineType: String) {
guard mineType.isMineType
else { return nil }
self.fileURL = fileURL
self.uuid = uuid
self.mineType = mineType
}
}
此时出现了3个堆区变量,在分配内存及引用计数层面上消耗内存比较大,因此需要优化
struct Attachment {
let fileURL: URL
let uuid: UUID
let mineType: MineType
init?(fileURL: URL, uuid: UUID, mineType: MineType) {
guard mineType.isMineType
else { return nil }
self.fileURL = fileURL
self.uuid = uuid
self.mineType = mineType
}
}
enum MineType: String{
case jpeg = "image/jpeg"
....
}
将uuid优化为UUID值类型,将mineType优化为enum值类型
3.初始化器
类初始器
class LGTeacher{
var age: Int
var name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
}
var t = LGTeacher(18, "Kody")
当前的类编译器不会自动提供成员初始化器
struct初始化器
struct LGTeacher {
var age: Int
var name: String
}
var t = LGTeacher(age: 18, name: "Kody")
当前的结构体编译器会自动提供成员初始化器(前提是我们没有指定初始化器)
便捷初始化器
class LGTeacher{
var age: Int
var name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
convenience init() {
self.init(18, "Kody")
}
}
class LGPerson: LGTeacher {
var subjectName: String
init(_ subjectName: String) {
//调用父类初始化器之前,自身类的属性必须初始化完成
self.subjectName = subjectName
super.init(18, "Kody")
}
}
var t = LGPerson("a")
这里我们记住:
- 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
- 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
- 便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括 同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。
- 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。
-
可失败初始化器
意味着当前因为参数不合法或外部条件不满足,存在初始化失败的情况。这种Swift中可失败初始化器写return nil语句,来表明可失败初始化器在何种情况下回触发初始化失败
class LGTeacher{
var age: Int
var name: String
init?(_ age: Int, _ name: String) {
if age < 18 { return nil}
self.age = age
self.name = name
}
convenience init?() {
self.init(18, "Kody")
}
}
var t = LGTeacher(17, "Kody")
print("end")
-
必要初始化器
在类的初始化器前添加required
修饰符来表明所有该类的子类如果要自定义初始化器
的话都必须实现该初始化器。
class LGPerson {
var age: Int
var name: String
required init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
convenience init() {
self.init(18, "Kody")
}
}
class LGTeacher: LGPerson {
var subjectName: String = ""
//自定义初始化器,必须实现required init(){}
init(_ subjectName: String) {
super.init(18, "Kody")
self.subjectName = subjectName
}
required init(_ age: Int, _ name: String) {
super.init(age, name)
}
//当然也可以添加一个便捷初始化器来完成这个功能,此时就不需要实现required init(){}
convenience init(_ age: Int, _ name: String, _ subjectName: String) {
self.init(age, name)
self.subjectName = subjectName
}
}
var t = LGTeacher(17, "kody")
print("end")
4.类的生命周期
iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,如图所示
OC通过clang编译器,编译成IR,然后再生成可执行文件.o(这里也就是我们的机器码)
Swift则是通过Swift编译器编译成iR,然后再生成可执行文件。
-
Parse
,语法分析,解析成抽象语法树 -
Sema
,语义分析。比如说当前的类型检查是否正确、是否安全 -
SILGen
,降级变成SILGen。SIL全称为Swift Interminal Language(Swift中间语言)
-
Raw SIL
,原生的SIL,没有开启优化选项 -
SILOpt Canonical SIL
,优化后的SIL。优化了一些额外的代码 -
IRGen和LLVM IR
,由LLVM降级成IR -
Machine Code
,由后端代码变为机器码(x86、arm64、...)
// 分析输出AST
swiftc main.swift -dump-parse
// 分析并且检查类型输出AST
swiftc main.swift -dump-ast
// 生成中间体语言(SIL),未优化
swiftc main.swift -emit-silgen
// 生成中间体语言(SIL),优化后的
swiftc main.swift -emit-sil
// 生成LLVM中间体语言 (.ll文件)
swiftc main.swift -emit-ir
// 生成LLVM中间体语言 (.bc文件)
swiftc main.swift -emit-bc
// 生成汇编
swiftc main.swift -emit-assembly
// 编译生成可执行.out文件
swiftc -o main.o main.swift
将main.swift输出成优化过后的SIL
class LGTeacher {
var age = 18
var name = "LG"
}
var t = LGTeacher()
sil_stage canonical
import Builtin
import Swift
import SwiftShims
import Foundation
class LGTeacher {
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
@_hasStorage @_hasInitialValue var t: LGTeacher { get set }
// t
sil_global hidden @$s4main1tAA9LGTeacherCvp : $LGTeacher
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>):
//alloc_global,分配一个全局变量
alloc_global @$s4main1tAA9LGTeacherCvp // id: %2
//拿到全局变量的地址给到%3寄存器
%3 = global_addr @$s4main1tAA9LGTeacherCvp : $*LGTeacher // user: %7
//%4寄存器存放LGTeacher元类型
%4 = metatype $@thick LGTeacher.Type // user: %6
// function_ref LGTeacher.__allocating_init()
// 定义一个方法引用给%5寄存器(函数的指针地址给到%5)
%5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
// 执行%5函数指针,参数为%4,并且把函数的返回值给到%6寄存器
%6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %7
//将%6存储到%3,实例变量的内存地址存到全局变量
store %6 to %3 : $*LGTeacher // id: %7
//定义一个Int32值,并且值为0,然后return 0。类似于OC代码中的main
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
return %9 : $Int32 // id: %10
} // end sil function 'main'
...
对于LGTeacher
-
@_hasStorage @_hasInitialValue
表示有一个初始化过的存储属性 - 这里的LGTeacher有2个初始化过的存储属性age、name
- @objc标记的deinit和默认的init函数
@main:入口函数,@
%0:寄存器,也可以理解成常量。一旦赋值不能修改。虚拟的,跑到设备上会使用真的寄存器
- 还原当前的名称,
xcrun swift-demangle
❯ xcrun swift-demangle s4main1tAA9LGTeacherCvp
$s4main1tAA9LGTeacherCvp ---> main.t : main.LGTeacher
找到寄存器%5中s4main9LGTeacherCACycfC
函数
// LGTeacher.__allocating_init()
sil hidden [exact_self_class] @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher {
// %0 "$metatype"
bb0(%0 : $@thick LGTeacher.Type):
%1 = alloc_ref $LGTeacher // user: %3
// function_ref LGTeacher.init()
%2 = function_ref @$s4main9LGTeacherCACycfc : $@convention(method) (@owned LGTeacher) -> @owned LGTeacher // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned LGTeacher) -> @owned LGTeacher // user: %4
return %3 : $LGTeacher // id: %4
} // end sil function '$s4main9LGTeacherCACycfC'
-
metadata
可以理解为isa -
alloc_ref
,进入SIL文档查询
Allocates an object of reference type T. The object will be initialized with retain count 1; its state will be otherwise uninitialized. The optional objc attribute indicates that the object should be allocated using Objective-C's allocation methods (+allocWithZone:).
大概意思为分配一个引用类型为T的对象,并且该对象的引用计数为1。也就是说在堆区上申请内存空间。如果对象标识为objc,将会使用Objective-C的+allocWithZone:方法申请内存空间。也就是oc的初始化方法
我们通过代码来查看2者的区别
class LGTeacher {
var age = 18
var name = "LG"
}
var t = LGTeacher()
进入汇编断点,callq
其实相当于函数调用。调用LGTeacher.__allocating_init()
,进入发现断点跑到了LGTeacher.__allocating_init
,在这里执行swift_allocObject
,并且执行初始化方法LGTeacher.init
。
class LGTeacher: NSObject {
var age = 18
var name = "LG"
}
var t = LGTeacher()
执行objc_allocWithZone
,并且使用objc_msgSend
发送init
消息
至此,已经通过代码来解释SIL文档中的alloc_ref
此时,我们可以通过Swift源码来分析_swift_allocObject_
- 进入到
HeapObject.cpp
文件,找到swift_allocObject
函数
//metadata,元数据类型。8字节
//requiredSize,需要的字节大小
//requiredAlignmentMask,对齐掩码。Swift对象8字节对齐,因此为7
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
assert(isAlignmentMask(requiredAlignmentMask));
auto object = reinterpret_cast(
swift_slowAlloc(requiredSize, requiredAlignmentMask));
// NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
// check on the placement new allocator which we have observed on Windows,
// Linux, and macOS.
new (object) HeapObject(metadata);
// If leak tracking is enabled, start tracking this object.
SWIFT_LEAKS_START_TRACKING_OBJECT(object);
SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);
return object;
}
进入swift_slowAlloc
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
void *p;
// This check also forces "default" alignment to use AlignedAlloc.
//# define MALLOC_ALIGN_MASK 15
if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
p = malloc(size);
#endif
} else {
size_t alignment = (alignMask == ~(size_t(0)))
? _swift_MinAllocationAlignment
: alignMask + 1;
p = AlignedAlloc(size, alignment);
}
if (!p) swift::crash("Could not allocate memory.");
return p;
}
swift_slowAlloc
其实就是在调用malloc
开辟内存空间
Swift对象内存分配:
Object.__allocating_init(编译器生成)
----> swift_allocObject
-----> _swift_allocObject_(HeapObject.cpp)
----> swift_slowAlloc(Heap.cpp)
----> malloc(Heap.cpp)
Swift对象的内存结构HeapObject
(OC objc_object
),有2个属性:metadata
,refCount
,默认占用16字节大小。
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *__ptrauth_objc_isa_pointer metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
#ifndef __swift__
HeapObject() = default;
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
// Initialize a HeapObject header for an immortal object
constexpr HeapObject(HeapMetadata const *newMetadata,
InlineRefCounts::Immortal_t immortal)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Immortal)
{ }
#ifndef NDEBUG
void dump() const SWIFT_USED;
#endif
#endif // __swift__
};
我们已经分析了实例对象的内存结构,接下来分析HeapMetadta
,也就是metadata
//其实就是TargetHeapMetadata类型,起了个别名
using HeapMetadata = TargetHeapMetadata;
点击进入TargetHeapMetaData
struct TargetHeapMetadata : TargetMetadata {
using HeaderType = TargetHeapMetadataHeader;
TargetHeapMetadata() = default;
constexpr TargetHeapMetadata(MetadataKind kind)
: TargetMetadata(kind) {}
#if SWIFT_OBJC_INTEROP
constexpr TargetHeapMetadata(TargetAnyClassMetadata *isa)
: TargetMetadata(isa) {}
#endif
};
TargetHeapMetaData继承自TargetMetaData
初始化方法中,如果是纯swift类,传入的参数就是MetadataKind。如果swift与OBJC交互的话,传入的参数就是isa。
关于MetadataKind的定义
name | value |
---|---|
Class | 0x0 |
Struct | 0x200 |
Enum | 0x201 |
Optional | 0x202 |
ForeignClass | 0x203 |
Opaque | 0x300 |
Tuple | 0x301 |
Function | 0x302 |
Existential | 0x303 |
Metatype | 0x304 |
ObjCClassWrapper | 0x305 |
ExistentialMetatype | 0x306 |
HeapLocalVariable | 0x400 |
HeapGenericLocalVariable | 0x500 |
ErrorObject | 0x501 |
LastEnumerated | 0x7FF |
此时我们继续进入TargetMetadata
探究,其实已经进入了一个瓶颈了。继续查看分析这个结构体
getTypeContextDescriptor() const {
switch (getKind()) {
case MetadataKind::Class: {
const auto cls = static_cast *>(this);
if (!cls->isTypeMetadata())
return nullptr;
if (cls->isArtificialSubclass())
return nullptr;
return cls->getDescription();
}
case MetadataKind::Struct:
case MetadataKind::Enum:
case MetadataKind::Optional:
return static_cast *>(this)
->Description;
case MetadataKind::ForeignClass:
return static_cast *>(this)
->Description;
default:
return nullptr;
}
}
根据不同的MetadataKind
类型来区分不同的数据类型。所以MetadataKind
是所有类型元类的最终基类
const auto cls = static_cast
当我的metadata是class类型的时候,将当前指针强转为TargetClassMetadata
类型
进入TargetClassMetadata
中有以下代码
constexpr TargetClassMetadata(const TargetAnyClassMetadata &base,
ClassFlags flags,
ClassIVarDestroyer *ivarDestroyer,
StoredPointer size, StoredPointer addressPoint,
StoredPointer alignMask,
StoredPointer classSize, StoredPointer classAddressPoint)
/// Swift-specific class flags.
ClassFlags Flags;
/// The address point of instances of this type.
uint32_t InstanceAddressPoint;
/// The required size of instances of this type.
/// 'InstanceAddressPoint' bytes go before the address point;
/// 'InstanceSize - InstanceAddressPoint' bytes go after it.
uint32_t InstanceSize;
/// The alignment mask of the address point of instances of this type.
uint16_t InstanceAlignMask;
/// Reserved for runtime use.
uint16_t Reserved;
/// The total size of the class object, including prefix and suffix
/// extents.
uint32_t ClassSize;
/// The offset of the address point within the class object.
uint32_t ClassAddressPoint;
进入TargetClassMetadata
父类TargetAnyClassMetadata
中有以下代码
TargetSignedPointer *
__ptrauth_swift_objc_superclass>
Superclass;
TargetPointer CacheData[2];
StoredSize Data;
看了这2个类,参考objc_class我们可以大胆的猜测这就是Swift类的数据结构
通过源码总结的Swift类的数据结构
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
通过代码来验证
struct HeapObject {
var metadata: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
class LGTeacher {
var age = 18
var name = "LG"
}
var t = LGTeacher()
//将t的指针重新绑定到HeapObject
//获取实例对象的指针
var objcRawPtr = Unmanaged.passUnretained(t).toOpaque()
//重新绑定到HeapObject
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objcPtr.pointee)
print("end")
执行结果
HeapObject(metadata: 0x00000001000081a0, refCount1: 3, refCount2: 0)
(lldb) x/8g 0x00000001000081a0
0x1000081a0: 0x0000000100008168 0x00007ff852c3f8b8
0x1000081b0: 0x00007ff8112eb800 0x0000803000000000
0x1000081c0: 0x000000010143f2f2 0x0000000000000002
0x1000081d0: 0x0000000700000028 0x00000010000000a8
(lldb)
- 0x0000000100008168 ----> isa
那么此时的metadata
我们是否也可以还原成Metadata(源码总结的)
结构体呢?
struct HeapObject {
var metadata: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
class LGTeacher {
var age = 18
var name = "LG"
}
var t = LGTeacher()
//将t的指针重新绑定到HeapObject
//获取实例对象的指针
var objcRawPtr = Unmanaged.passUnretained(t).toOpaque()
//重新绑定到HeapObject
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objcPtr.pointee)
//MemoryLayoutce,Swift中测量数据类型的大小
let metadataPtr = objcPtr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout.stride)
print(metadataPtr.pointee)
print("end")
执行结果
HeapObject(metadata: 0x00000001000081a8, refCount1: 3, refCount2: 0)
Metadata(kind: 4295000432, superClass: _TtCs12_SwiftObject, cacheData: (140703416891392,
140943646785536), data: 4485824738, classFlags: 2, instanceAddressPoint: 0, instanceSize: 40,
instanceAlignmentMask: 7, reserved: 0, classSize: 168, classAddressPoint: 16, typeDescriptor:
0x0000000100003c4c, iVarDestroyer: 0x0000000000000000)
(lldb)
-
superClass: _TtCs12_SwiftObject
,父类为_TtCs12_SwiftObject
-
instanceSize: 40
,实例大小为16(metadata+refCount)+24(age+name(8+8)) -
instanceAlignmentMask: 7
,8字节对齐掩码
至此,通过代码已经证明了MetaData的数据结构