前言
继 类的底层原理(一) 的探索后,已理解 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。