iOS底层-类的底层原理(二)

前言

继 类的底层原理(一) 的探索后,已理解 isa指针指向类的结构 。下面继续探索类的底层原理,并做相应的补充。

准备工作

WWDC-关于 runtime 的改进优化

LLVM源码

成员变量的底层原理

在分析 类的底层原理(一) 时,只分析了 propertiesmethods

  • propertiesmethods 都在 class_rw_t 中。

  • 而成员变量 ivars 存在于class_rw_tro()中,也就是 class_ro_t

class_ro_t 结构如下:

案例一

通过案例分析 ivars ,添加如下代码 :

打印结果:

此时,ivars 都存在于 ivar_list_t 中,使用C++数组 get() 获取每个成员变量。

成员变量、属性、实例变量的区别

  • 成员变量在类的 {} 中,以 基本数据类型 声明的变量,例如:NSString、int、float、double、char、bool。

  • 属性是用 @property 修饰的,在底层会变成 _ 方式的 成员变量 ,也会自动生成 getset 方法。 属性 = _成员变量 + set + get

  • 实例变量是以 对象类型 声明的 (特殊的成员变量),例如 NSObject *pp 就是实例变量。

案例二

1. 创建一个 Project,添加如下代码:

2.通过 clang 编译并查看编译文件:

$ clang -rewrite-objc main.m -o main.cpp

属性在编译时会变成 _的成员变量,并生成对应的 setget 方法。

但是其 set 实现方式却不一样:

  • name 是通过 objc_setProperty 方法实现的

  • addressage 则是通过内存地址偏移的方式存储的

为什么会不一样?namecopy 修饰,猜想是和 copy 修饰符有关。

分析 objc_setProperty

为什么会有 objc_setProperty 的存在?

当创建一个 属性 时,调用 set 存数据时,不可能每创建一个 属性 就在底层生成一个对应的 set 方法,这样对内存开销太大了。于是就有了 objc_setProperty 方法,不管上层是什么 set 方法,统一调用 objc_setProperty 方法。这个过程就是 SELIMP 过程。

SEL 就是方法名字,IMP 就是底层方法实现。

由于 objc_setProperty 是需要在编译时直接创建,所以 objc_setProperty 需要去 LLVM源码 中查找。

1. 定位 objc_setProperty 方法

2. 定位 getSetPropertyFn 方法

3. 定位 GetPropertySetFunction 方法

GetPropertySetFunction 方法调用,是在 switch 选择 strategy.getKind()PropertyImplStrategy::GetSetProperty 或者 PropertyImplStrategy::SetPropertyAndExpressionGet 时执行的。

那么 strategy 是什么意思?什么时候给它赋值?每个 case 都有什么意义?

4. 分析 PropertyImplStrategy

5. PropertyImplStrategy 构造函数

结论:

  • 只要是设置了 copy 属性,不管是不是原子性,都没有影响,set 方法都会被重定向到objc_setProperty

  • 如果不设置属性(除原子性之外),那么默认属性是 strong ,不会触发 objc_setProperty

分析 objc_getProperty

同样 objc_getProperty 也需要去 LLVM源码 中查找。

1. 定位 objc_getProperty 方法

2. 定位 getGetPropertyFn 方法

3. 定位 GetPropertyGetFunction 方法

类方法的底层原理

lldb 验证流程如下:

总结:

  • 对象方法 存储在自身

  • 类方法 存储在 元类 中,并且以 对象方法 存在于 元类 中。

  • 没有所谓的 类方法 之说,所有的方法都是 对象方法,其底层都是 函数

补充:类型编码 TypeEncoding

在上面的 main.cpp 中,搜索 setName 发现有一些奇奇怪怪的符号。

其中 "v24@0:8@16""@16@0:8" 其实是 类型编码 。下面具体看一下什么是 类型编码

官网关于 TypeEncoding 的解释。归根结底,其实就是对照着下面的 符号表 去分析编码代码。

TypeEncoding符号表

那么以上面 setName 为例,具体分析 v24@0:8@16 的实际意义。

补充:面试题

1. 为什么获取 元类类方法 也可以得到 类方法?(已证明:类方法对象方法 形式存储在 元类 中)

打印结果如下:

分析底层方法:

分析得出:所谓的 类方法 其实就是获取 元类对象方法

分析 getMeta() 方法:

分析得出:如果是 元类 ,则返回 元类本身 ,否则返回 元类isa。这就是为什么获取 元类类方法 也可以得到 类方法的原因。

2. 关于 isKindOfClass

案例分析:

打印结果:

并且也在源码中找到 isMemberOfClassisKindOfClass 方法,也打上断点,来具体分析其原理:

但是执行过程中,却没有执行 isKindOfClass 方法,只执行了 isMemberOfClass 方法。先来分析 isMemberOfClass

isMemberOfClass

分析源码如下:

  • + isMemberOfClass: 是获取类的 元类 进行比较

  • - isMemberOfClass: 是获取 类对象 进行比较

分析上图代码:

  • re1NSObject 调用 + isMemberOfClass:NSObject 比较。因此 NSObject元类NSObject 并不相等,所以是0。
  • re3ZLObject 调用 + isMemberOfClass:ZLObject 比较。因此 ZLObject元类ZLObject 并不相等,所以是0。
  • re5NSObject对象 调用 - isMemberOfClass:NSObject 比较。因此 NSObject对象 的类是NSObject,与 NSObject 相等,所以是1。
  • re7ZLObject对象 调用 - isMemberOfClass:ZLObject 比较。因此 ZLObject对象 的类是ZLObject,与 ZLObject 相等,所以是1。

isKindOfClass

上述案例中,isKindOfClass 没有执行,只有 isMemberOfClass 相关方法执行了。这是什么原因呢?

具体分析汇编才知道,isKindOfClass没有执行的原因是底层执行了 objc_opt_isKindOfClass 方法。

具体分析 objc_opt_isKindOfClass

在上面的代码中,不管上层调用的是 + isKindOfClass: 还是 - isKindOfClass:,内部都会重定向到 objc_opt_isKindOfClass 这个方法。因为 其本质也是一个对象,我们称之为 类对象。所以obj每次都有值。

分析源码如下:

  • 如果是类:首先获取类的元类 比较。相等则返回true。如果不相等,再获取类的 父类 比较,相等则返回true。否则循环找 父类 比较,直到获取到的父类为 nil,依旧没有找到则返回false。

  • 如果是实例对象,首先获取 比较。相等则返回true。否则和 的步骤一样。

分析上图代码:

  • re2NSObject 的类,获取 元类NSObject 不等,继续寻找获取 元类的父类NSObjectNSObject 相等,返回1。
  • re4ZLObject的类,获取 元类ZLObject 不等,继续寻找获取 元类的父类NSObject的元类 依旧不等,继续往上 NSObject元类的父类NSObject 依旧不等,再往上就是nil ,最后返回0。
  • re6NSObject对象,获取 NSObject,与NSObject相等,返回1。
  • re8ZLObject对象,获取 ZLObject,与NSObject相等,返回1。

你可能感兴趣的:(iOS底层-类的底层原理(二))