Objective-C runtime - 分类与关联对象
前言
系列第三篇:深入分析OC中的分类 - Category 以及在分类里面会涉及到了 关联对象
Category
category_t
先来看看category的真面目:category_t
1 | struct category_t { |
category_t
结构体定义在objc-runtime-new.h
中,可以看出该结构体中含有:实例方法列表、类方法列表、协议列表、属性列表。
编译之后,runtime之前
来看看NSObject的分类:
1 | #import <Foundation/Foundation.h> |
在编译结束之后,runtime运行之前。NSObject被编译成一个objc_class
结构体;而NSObject (vc) 被编译成一个category_t
结构体。
打印出这个结构体:(在attachCategories
方法中判断当前的category位vc,并进入断点)
打印出的category结果如下:
runtime之后
运行attachCategories
方法之后,会将category中的所有方法、属性、协议添加到类的class_rw_t
中。
我们以添加方法为例,在添加任何的分类方法之前,NSObject的class_rw_t
中的methods
数组只有一个元素,也就是class_ro_t
中的baseMethodList
列表。添加一个分类之后,会往methods
数组中添加一个元素,该元素的类型是entsize_list_tt
。
使用lldb查看,发现在添加vc分类后,NSObject里面已经添加了64个分类了;加上baseMethodList
,methods
数组已经有65个元素。(后添加的分类在数组中的位置靠前,baseMethodList元素在最后面)
例外
对于系统自带的类,才会在runtime时加载分类;但是对于自己创建的类,在编译结束之后就直接将分类的方法添加到baseMethodList
中了,就好像没有分类一说。。
1 | // TestOBJ |
在_objc_init
里面打上断点,这个方法是runtime的入口函数。在这个时候使用lldb,就发现在TestOBJ的baseMethodList
里面有2个test方法了,当然分类的test方法在前面,也就是会被先检索到。
关联对象
观察发现,category里面有属性,但是没有实例变量。在系列上一篇Objective-C runtime - 属性与方法了解到,属性没什么用,真正存放的还是实例变量。
那么为什么不能添加实例变量呢? 因为实例变量在每个对象中都有一份。对于使用分类之前,对象的内存是如此分布的;如果添加了分类,那么每个对象的内存结构都需要改变。而对于方法来说,只需要类的结构体来存储方法,类的结构体也就只有一个,就可以随意更改。
那么为什么可以添加属性呢? 只添加属性不添加实例变量,毫无意义。正是因为关联对象的引入,使得这个做法有意义起来。因为关联对象的作用和实例变量相似。
源码分析
关联对象相关的源码全部放在objc-reference.h
和 objc-reference.mm
两个文件中。
关联对象和对象是一一对应的,而不是和类一一对应的
- 所有的关联对象都由一个AssociationsManager对象来管理,这个对象里面有一个AssociationsHashMap。
- AssociationsHashMap由许多key-value构成。key是对象的地址;value是一个ObjectAssociationMap,也就是所谓的关联对象。
- ObjectAssociationMap就是关联对象,每个关联对象里面包含多个key-value对。key是属性名;value是ObjcAssociation,也就是相当于属性对应的实例变量。
- ObjcAssociation相当于实例变量,该结构体有两个成员:**_policy** 属性的内存管理语义;**_value** 属性的值
总结
- category可以给既有类添加属性、方法、协议,但是不能添加实例变量
- 添加属性对应的实例变量可以使用关联对象来实现
- category里面的方法不会覆盖原有的同名方法,只是会被提前检索到。所以感官上觉得被覆盖掉了
- 对于系统自带的类,在runtime时才会加载category;但是自定义类的分类在编译期就已经全部加载了,就像不存在分类,但是存在一个大大的自定义类一样。