Objective-C 分类的实现原理,阅读runtime源码会发现,分类在运行时的结构是这个样子的:
1 | struct category_t { |
name代表的是分类的名称;cls是分类所属的宿主类;classMethods是实例方法列表;classMethods类方法列表;protocols是协议列表;instanceProperties是属性列表。从中可以发现,可以给分类添加实例方法、类方法、协议、以及属性。
接下来看一下分类在运行时的加载调用栈:
1 _objc_init
2 map_3_images
3 map_images_nolock
4 _read_images
5 remethodizeClass
事实上,分类在运行时加载的入口是从remethodizeClass的方法开始的。该方法的功能是:附加外部的分类到已经存在的类里面;修改方法列表、协议列表、属性列表,更新方法缓存以及其子类。
我们先来看一下这个方法的内部实现,然后一步步的探讨系统是如何给宿主类添加分类的。
1 | static void remethodizeClass(Class cls) |
方法中的isMeta变量是用来判断我们的分类中是添加的类方法,还是实例方法;
if语句中的unattachedCategoriesForClass方法就是用来获取cls中没有被附加的分类列表。
分支里面的attachCategories方法就是用来附加分类的方法列表、属性列表、协议列表到宿主类中的核心方法。
接着看attachCategories方法的内部实现:
1 | static void attachCategories(Class cls, category_list *cats, bool flush_caches) |
isMeta变量的含义同上文,依然是判断添加的方法是实例方法还是类方法。这里我们只讨论为分类添加实例方法的情况,mlists、proplists、protolists这三个都是二维数组,分别代表方法列表、属性列表、协议列表。我这里主要分析关于方法添加的逻辑。method_list_t结构存储的是method_t结构这里它代表一个方法,关于这两个的具体内容大家可以去runtime源码里面找答案。
接下来是代表方法数量的mcount变量,属性数量的propcount变量,协议数量的protocount变量;i指的是宿主类分类的总数。
接下来可以看到是一个倒序的while循环,倒序的含义就是最先访问最后编译的分类,因为cats中的分类是按照编译顺序添加的。
这就抛出来一个问题:如果一个类有两个分类,这两个分类有一个同名方法,那么这两个方法哪个会生效?
答案就是取决于两个方法所属的分类的编译顺序,就是说看两个分类哪一个最后被加入到cats中。
经过一系列的处理之后呢,就会执行method.attachLists方法来将分类的方法列表添加到宿主类中。
接下来看一下method.attachLists这个方法的内部实现:
1 | /* |
addedLists是经过处理的方法列表。oldCount是原有元素总数。newCount是拼接之后的元素总数,realloc会重新按照拼接后的总数重新分配空间。然后重新设置元素总数。接下来调用memove移动内存,会将原来的元素向后移动,新拼接的元素会插入到原有元素的前面。最后,调用memcpy拷贝内存,拷贝后的内存存储结构如注释中所示。这也就是分类方法会“覆盖“宿主类方法的原因。
到此为止,分类中的方法已经拼接到宿主类中了。