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
拷贝内存,拷贝后的内存存储结构如注释中所示。这也就是分类方法会“覆盖“宿主类方法的原因。
到此为止,分类中的方法已经拼接到宿主类中了。