本文由快学吧个人写作,以任何形式转载请表明原文出处。
一、资料准备
对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。
二、思路
- 分类的本质是
category_t结构体,但是分类添加属性的时候,在分类的结构体中的_method_list_t(也就是方法实现中),是找不到分类添加的属性的setter和getter方法的。那么如何将分类的属性变成和类的属性一样呢?就是说,如何让分类的属性也可以在外部调用的时候,可以直接进行赋值呢?解决的方法就是关联对象。 - 关联对象的实现原理是什么?
- 类扩展和分类的区别是什么?类扩展的本质是什么?
三、类扩展
类扩展相对用的更多一些,也更简单,所以先说类扩展。
1. 类扩展的本质
在818.2的项目中,在main.m中创建一个类,并添加扩展,然后通过clang,查看编译后的main.m,来查看类扩展的本质。
main.m的代码 :
编译成C++用的clang命令,一定要进入到main.m所在的文件夹之后,再使用 :
clang -rewrite-objc main.m -o main.cpp
打开生成的main.cpp文件,找到JDMan :
可以得出一个推断,类扩展的数据在编译期就已经直接放入类中了。可以在main.m中加点代码进行测试验证。
main.m中的main函数中添加 :
利用lldb查看rw和ro中的数据 :
由上述可以证明推断 :
类扩展的数据是在编译期就放入类的ro中的。其本质就是类。没有单独的结构。
2. 类扩展的添加方式
- 直接在类的.m文件中声明,由于声明长得很像分类,所以类扩展也叫匿名分类,但是实际上它和分类是有本质区别的 :
- 创建类扩展文件 :
和分类的创建路径是一样的,只不过要将Category换成Extention
和上面的那种是一样的。
小结
类扩展的数据是直接在编译期就放入到类的结构中的。但是如果是用文件形式的,一定要记得引入头文件,不然是没有用的。
四、关联对象
因为分类和类扩展经常被放到一起做对比,有人经常说,分类不能添加属性,只能添加方法,这就是和类扩展的区别。但是实际在开发过程中,分类也是可以添加属性的,只不过不是添加到类的ro中,因为ro是编译时就确定的,是只读的,而分类是运行时的数据,数据会添加在rw和rwe中,这才是最大的区别。
那么根据常用的给分类添加属性的setter和getter方法的实践,最重要的一点就是runtime的api,也就是关联对象。
1. 常见的关联对象使用场景
创建一个类JDMan、创建JDMan的分类JDMan(LA),在JDMan(LA)中创建一个属性。
JDMan :
JDMan(LA) :
main.m :
未给分类的属性添加setter和getter方法时 :
如果代码就如上,那么在main.m中创建JDMan的实例,调用JDMan(LA)中的属性的时候,一定是出错的。因为分类的属性默认是没有添加setter和getter方法的,如下图 :
利用关联对象给分类的属性添加setter和getter方法之后 :
再次调用,main.m代码和运行结果如下 :
main.m代码,调用了分类属性的setter和getter方法 :
运行结果 :
这是最常见也是经常用的一种方式。其核心就是runtime的关联对象,也就是objc_setAssociatedObject和objc_getAssociatedObject。
2. objc_setAssociatedObject和objc_getAssociatedObject
一张图说明 :
其实就是有一个关联对象的管理者,无论是创建关联对象,还是获取关联对象的值,都要通过这个管理者,才可以从关联对象的哈希表中根据对应的key取到value。
总的关联对象表是一张大的哈希表,里面存储的是每个对象(类)的小的哈希表,也就是说大的哈希表是一张二级表。表中存表。小的哈希表获取如下 :
设置关联对象 :
(1). 如果key能查找到,也就是在关联表中存在,以前存过,直接存储到上面已经创建或者找到的小关联表中,相当于替换。
(2). 如果key查不到,则直接存储进去。
(3). 如果设置的value是nil,则直接抹除key对应的value,相当于取消了关联对象。
获取关联对象 :
直接根据key,利用管理者获取大的关联表,然后找到小的关联表,拿到对应的value。