前言
继 类的底层原理(一) 的探索后,已理解 isa指针指向 和 类的结构 。下面继续探索类的底层原理,并做相应的补充。
准备工作
成员变量的底层原理
在分析 类的底层原理(一) 时,只分析了 properties 和 methods。
properties和methods都在class_rw_t中。而成员变量
ivars存在于class_rw_t的ro()中,也就是class_ro_t。

class_ro_t 结构如下:

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

打印结果:

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

成员变量、属性、实例变量的区别
成员变量在类的
{}中,以基本数据类型声明的变量,例如:NSString、int、float、double、char、bool。属性是用
@property修饰的,在底层会变成_方式的成员变量,也会自动生成get和set方法。属性 = _成员变量 + set + get实例变量是以
对象类型声明的 (特殊的成员变量),例如NSObject *p,p就是实例变量。
案例二
1. 创建一个 Project,添加如下代码:

2.通过 clang 编译并查看编译文件:
$ clang -rewrite-objc main.m -o main.cpp

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

但是其
set实现方式却不一样:
name是通过objc_setProperty方法实现的而
address和age则是通过内存地址偏移的方式存储的
为什么会不一样?name 是 copy 修饰,猜想是和 copy 修饰符有关。
分析 objc_setProperty
为什么会有 objc_setProperty 的存在?
当创建一个
属性时,调用set存数据时,不可能每创建一个属性就在底层生成一个对应的set方法,这样对内存开销太大了。于是就有了objc_setProperty方法,不管上层是什么set方法,统一调用objc_setProperty方法。这个过程就是SEL→IMP过程。
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 的解释。归根结底,其实就是对照着下面的
符号表去分析编码代码。

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

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


打印结果如下:

分析底层方法:

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

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

打印结果:

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

但是执行过程中,却没有执行 isKindOfClass 方法,只执行了 isMemberOfClass 方法。先来分析 isMemberOfClass
isMemberOfClass
分析源码如下:
+ isMemberOfClass:是获取类的元类与类进行比较
- isMemberOfClass:是获取类对象与类进行比较
分析上图代码:
re1是NSObject调用+ isMemberOfClass:与NSObject比较。因此NSObject元类与NSObject并不相等,所以是0。re3是ZLObject调用+ isMemberOfClass:与ZLObject比较。因此ZLObject元类与ZLObject并不相等,所以是0。re5是NSObject对象调用- isMemberOfClass:与NSObject比较。因此NSObject对象的类是NSObject,与NSObject相等,所以是1。re7是ZLObject对象调用- 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。否则和类的步骤一样。
分析上图代码:
re2是NSObject的类,获取元类与NSObject不等,继续寻找获取元类的父类为NSObject与NSObject相等,返回1。re4是ZLObject的类,获取元类与ZLObject不等,继续寻找获取元类的父类为NSObject的元类依旧不等,继续往上NSObject元类的父类为NSObject依旧不等,再往上就是nil,最后返回0。re6是NSObject对象,获取类为NSObject,与NSObject相等,返回1。re8是ZLObject对象,获取类为ZLObject,与NSObject相等,返回1。






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)