iOS之武功秘籍④:类结构分析

2021-02-20  本文已影响0人  長茳

iOS之武功秘籍 文章汇总

写在前面

通过前面篇章的探索,我们已成功的从对象过渡到类了.本文就来讲讲实例出实例对象的类以及类的结构.

本节可能用到的秘籍Demo

一、类的本质

① 类的本质

objc源码下准备代码

利用clangOC文件输出cpp文件.(xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp)

发现类在底层用Class接收,然后开始大海捞针(开天眼模式)找到了Class的定义

想要找到objc_class就搜不到了,但是总觉得它似曾相似,或许能在objc源码中找到灵感

在源码中搜索代码的经验 "objc_class :"、 "objc_class {"

我们发现objc_class继承于objc_object

结论:类的本质是objc_class类型的结构体,objc_class继承于objc_object,所以满足万物皆对象

② objc_class & objc_object 、objc_object和NSObject的关系

objc_object和NSObject

等等,为什么继承objc_object就满足万物皆对象了???
看过NSObject的定义就知道了

仔细比较的话就能看出NSObjectobjc_object有着说不清道不明的关系

objc_class & objc_object

前面使用clang编译过main.m文件,从编译后的c++文件中可以看到如下c++源码

那么objc_classobjc_object 到底有什么关系?
通过上述的源码查找以及main-arm64.cpp中底层编译源码,有以下几点说明:

引申问题:objc_object 与 对象的关系

③ Class isa

isa明明是isa_t类型的,为什么注释了一句Class ISA

④总结

objc_class、objc_object、isa、object、NSObject等的整体的关系,如下图所示

二、指针内存偏移

在分析类结构之前,需要先了解内存偏移,因为类信息中访问时,需要使用内存偏移

① 普通指针 - 值拷贝

我们观察上面的代码,虽然整型变量ab都是被赋值为10,但是ab内存地址是不一样的,这种方式被称为值拷贝.

② 对象 - 指针拷贝或引用拷贝

通过运行结果,可以知道obj1obj2对象不光自身内存地址不一样,连指向的对象的内存地址也不一样,这种方式被称为指针拷贝引用拷贝.

我们可以用一幅图来总结上面的两个例子:

③ 用数组指针引出 - 内存偏移

通过运行结果可以看到:

三、类的结构

objc_class的定义可以得出,类有4个属性:isa、superclass、cache、bits

① Class ISA

不但实例对象中有isa指针类对象中也有isa指针关联着元类

Class本身就是一个指针,占用8字节

② Class superclass

顾名思义就是类的父类(一般为NSObjectsuperclassClass类型,所以占用8字节

③ cache_t cache

虽然对这个属性比较陌生(后面章节会详细介绍),但是cache在英文中的意思是缓存
cache_t是一个结构体,内存长度由所有元素决定:_bucketsAndMaybeMasklong类型,它是一个指针,占用8字节
mask_t是个uint32_t类型,_mask占用4字节;因_occupied_flags都是uint16_t类型,uint16_tunsigned short 的别名,所以_occupied占用2字节;_flags占用2字节
=>cache_t占用16字节

④ class_data_bits_t bits

又是一个陌生的属性,但是苹果工程师还是蛮友好的,这一看就是存数据的地方

那么问题来了,类的属性方法都去哪儿了?是在cache还是在bits? 其实前文中有提到一丢丢——objc_class中有个class_rw_t *data()方法

通过前面的分析可知,想要获取bits的中的内容,只需通过类的首地址平移32字节即可.

四、类的属性方法

TCJPerson添加hobby成员属性、tcj_name属性变量、sayNB类方法、sayHello实例方法

① 类的属性

x/4gx cls打印当前类结构

bits刚好是类的内存首地址+isa、superclass、cache的内存长度
=> 0x1000081f0+32字节 = 0x100008210

po打印不出来,那就类型强转打印输出bits的内存地址

根据class_rw_t *data() { return bits.data(); }打印bits.data()

$4指针的打印结果中可以看出bits中存储的信息,其类型是class_rw_t,也是一个结构体类型.但我们还是没有看到属性列表、方法列表等,需要继续往下探索.

通过查看class_rw_t定义的源码发现,结构体中有提供相应的方法去获取属性列表、方法列表等,如下所示

接下来继续探索 bits中的属性列表,以下是 LLDB 探索的过程

那么我们的hobby去哪了呢?难道我TCJPerson类只配拥有“国籍”不配拥有“姓名”?让我静静...

由此我们知道class_rw_t中的property_list只有属性,没有成员变量,属性与成员变量的区别就是有没有set、get方法,如果有,则是属性,如果没有,则是成员变量.

在一顿猛如虎的操作(开天眼)之后,发现了class_rw_t有个属性class_ro_t.

在控制台输出ro,跟class_ro_t的结构类型一摸一样.

而我们的成员变量就在roivars里面,好期待啊

如愿拿到了hobbytcj_name,但是这个tcj_name长得有点不一样.
仔细想想这不就是编译器会在底层自动将属性变量生成一个成员变量 _tcj_name(_前缀+属性变量),嗦嘎撕裂,还有谁...

小总结:

② 类的方法

③ 结论

④ API验证

利用底层开放的API可以验证以上结论

五、提出疑问

① 类存在几份?

由于类的信息内存永远只存在一份,所以 类对象只有一份.

② objc_object 与 对象的关系
③ 什么是 属性 & 成员变量 & 实例变量 ?
④ 成员变量 和 实例变量什么区别?
⑤ isKindOfClass 和 isMemberOfClass 的理解

这是一道涉及isa走位图的面试题,大胆猜测下结果

先来探索一下isKindOfClassisMemberOfClass的实现

上图中我已做了详细的解析,接下来结合isa走位图(实线为父类走向)可以得出前面四个打印结果:

后面四个结果分析如下:

六、补充知识

strong & copy & weak 底层分析

clang编译的cpp文件中可以发现 strong & copy & weak 修饰的属性在编译的底层代码中是有区别的

这里就有疑问了,为什么copy修饰的属性使用了objc_setProperty,而strong修饰的没有?

从这里即可看出,针对不同的修饰符,返回的是不同的

上述的几个name分别对应objc4-818.2源码中的如下方法

然后通过汇编调试发现,最终都会走到objc_storeStrong

其中BlockCaptureEntityKind有如下的枚举值以及表示的含义

结论

② Type Encoding & Property Type String

Type Encoding-官方文档
Property Type String-官方文档

clang中的方法签名

Type encoding -- clang编译后,方法列表的这些字符的含义是什么?

@16@0:8为例

更多的可以查看官网的以下列表

clang编译后的属性的attribute

clang编译输出了属性的attribute,同样也可以通过property_getAttributes方法获取

更多的可以查看官网的以下列表

写在后面

和谐学习,不急不躁.我还是我,颜色不一样的烟火.

上一篇 下一篇

猜你喜欢

热点阅读