前言
继 类的底层原理(一) 的探索后,已理解 isa指针指向
和 类的结构
。下面继续探索类的底层原理,并做相应的补充。
准备工作
成员变量的底层原理
在分析 类的底层原理(一) 时,只分析了 properties
和 methods
。
properties
和methods
都在class_rw_t
中。而成员变量
ivars
存在于class_rw_t
的ro()
中,也就是class_ro_t
。
![](https://img.haomeiwen.com/i3014057/d8f95ea6bc85d6a8.png)
class_ro_t
结构如下:
![](https://img.haomeiwen.com/i3014057/feffe809a0daed63.png)
案例一
通过案例分析 ivars
,添加如下代码 :
![](https://img.haomeiwen.com/i3014057/98b4b8d30445ba68.png)
打印结果:
![](https://img.haomeiwen.com/i3014057/c88612712a8317a9.png)
此时,ivars
都存在于 ivar_list_t
中,使用C++数组 get()
获取每个成员变量。
![](https://img.haomeiwen.com/i3014057/986f7a95fcbc5bc8.png)
成员变量、属性、实例变量的区别
成员变量在类的
{}
中,以基本数据类型
声明的变量,例如:NSString、int、float、double、char、bool。属性是用
@property
修饰的,在底层会变成_
方式的成员变量
,也会自动生成get
和set
方法。属性 = _成员变量 + set + get
实例变量是以
对象类型
声明的 (特殊的成员变量),例如NSObject *p
,p
就是实例变量。
案例二
1. 创建一个 Project
,添加如下代码:
![](https://img.haomeiwen.com/i3014057/ed95fee866c26198.png)
2.通过 clang
编译并查看编译文件:
$ clang -rewrite-objc main.m -o main.cpp
![](https://img.haomeiwen.com/i3014057/733ce855f3cdf1ab.png)
属性在编译时会变成
_
的成员变量,并生成对应的set
和get
方法。
![](https://img.haomeiwen.com/i3014057/326b35b306c5f584.png)
但是其
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
方法
![](https://img.haomeiwen.com/i3014057/e010efc2583bfd0b.png)
2. 定位 getSetPropertyFn
方法
![](https://img.haomeiwen.com/i3014057/8e70691feec05b2b.png)
3. 定位 GetPropertySetFunction
方法
![](https://img.haomeiwen.com/i3014057/f7a1dd1bd51ac6e6.png)
GetPropertySetFunction
方法调用,是在switch
选择strategy.getKind()
为PropertyImplStrategy::GetSetProperty
或者PropertyImplStrategy::SetPropertyAndExpressionGet
时执行的。那么
strategy
是什么意思?什么时候给它赋值?每个case
都有什么意义?
4. 分析 PropertyImplStrategy
![](https://img.haomeiwen.com/i3014057/ff5f86a4b515e82b.png)
5. PropertyImplStrategy
构造函数
![](https://img.haomeiwen.com/i3014057/d140fa07ffc0a8e0.png)
结论:
只要是设置了
copy
属性,不管是不是原子性,都没有影响,set
方法都会被重定向到objc_setProperty
。如果不设置属性(除原子性之外),那么默认属性是
strong
,不会触发objc_setProperty
。
分析 objc_getProperty
同样 objc_getProperty
也需要去 LLVM源码
中查找。
1. 定位 objc_getProperty
方法
![](https://img.haomeiwen.com/i3014057/385d8c8734e2595d.png)
2. 定位 getGetPropertyFn
方法
![](https://img.haomeiwen.com/i3014057/d36c0c701ff7a693.png)
3. 定位 GetPropertyGetFunction
方法
![](https://img.haomeiwen.com/i3014057/ff89f69ced8177d2.png)
类方法的底层原理
lldb
验证流程如下:
![](https://img.haomeiwen.com/i3014057/0252d8f8489625ae.png)
总结:
对象方法
存储在自身类
中
类方法
存储在元类
中,并且以对象方法
存在于元类
中。没有所谓的
类方法
之说,所有的方法都是对象方法
,其底层都是函数
。
补充:类型编码 TypeEncoding
在上面的 main.cpp
中,搜索 setName
发现有一些奇奇怪怪的符号。
![](https://img.haomeiwen.com/i3014057/a6d0d01ba24d449b.png)
其中 "v24@0:8@16"
、"@16@0:8"
其实是 类型编码
。下面具体看一下什么是 类型编码
。
官网关于 TypeEncoding 的解释。归根结底,其实就是对照着下面的
符号表
去分析编码代码。
![](https://img.haomeiwen.com/i3014057/457c11a23c008a43.png)
那么以上面 setName
为例,具体分析 v24@0:8@16
的实际意义。
![](https://img.haomeiwen.com/i3014057/bed2e5abb47096fc.png)
补充:面试题
1. 为什么获取 元类
的 类方法
也可以得到 类方法
?(已证明:类方法
以 对象方法
形式存储在 元类
中)
![](https://img.haomeiwen.com/i3014057/7d1f46b0fbe70146.png)
![](https://img.haomeiwen.com/i3014057/df4722b4fb352530.png)
打印结果如下:
![](https://img.haomeiwen.com/i3014057/4fe57792805e34c5.png)
分析底层方法:
![](https://img.haomeiwen.com/i3014057/0204f3ee50d1bd5e.png)
分析得出:所谓的
类方法
其实就是获取元类
的对象方法
。
分析 getMeta()
方法:
![](https://img.haomeiwen.com/i3014057/03e911b61a0c871b.png)
分析得出:如果是
元类
,则返回元类本身
,否则返回元类isa
。这就是为什么获取元类
的类方法
也可以得到类方法
的原因。
2. 关于 isKindOfClass
案例分析:
![](https://img.haomeiwen.com/i3014057/0e2dd1a635f345e2.png)
打印结果:
![](https://img.haomeiwen.com/i3014057/8f7d3a53fbffb452.png)
并且也在源码中找到 isMemberOfClass
和 isKindOfClass
方法,也打上断点,来具体分析其原理:
![](https://img.haomeiwen.com/i3014057/48bbdb155007aea0.png)
但是执行过程中,却没有执行 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
方法。
![](https://img.haomeiwen.com/i3014057/88369cd7a11b7d9b.png)
具体分析 objc_opt_isKindOfClass
:
![](https://img.haomeiwen.com/i3014057/5b5cd0c3ef3a0858.png)
在上面的代码中,不管上层调用的是 + 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。
网友评论