绝大多数 iOS 开发者在学习 runtime 时都阅读过 runtime.h 文件中的这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; |
可以看到其中保存了类的实例变量,方法列表等信息。
不知道有多少读者思考过 OBJC2_UNAVAILABLE
意味着什么。其实早在 2006 年,苹果在 WWDC 大会上就发布了 Objective-C 2.0,其中的改动包括 Max OS X 平台上的垃圾回收机制(现已废弃),runtime 性能优化等。
这意味着上述代码,以及任何带有 OBJC2_UNAVAILABLE
标记的内容,都已经在 2006 年就永远的告别了我们,只停留在历史的文档中。
Category 的原理
虽然上述代码已经过时,但仍具备一定的参考意义,比如 methodLists
作为一个二级指针,其中每个元素都是一个数组,数组中的每个元素则是一个方法。
接下来就介绍一下 category 的工作原理,在美团的技术博客 深入理解Objective-C:Category 中已经有了非常详细的解释,然而可能由于时间问题,其中的不少内容已经过时,我根据目前最新的版本(objc-680) 做一些简单的分析,为了便于阅读,在不影响代码逻辑的前提下有可能删除部分无关紧要的内容。
概述
首先 runtime 依赖于 dyld 动态加载,在 objc-os.mm 文件中可以找到入口,它的调用栈简单整理如下:
1 2 3 4 |
void _objc_init(void) └──const char *map_2_images(...) └──const char *map_images_nolock(...) └──void _read_images(header_info **hList, uint32_t hCount) |
以上四个方法可以理解为 runtime 的初始化过程,我们暂且不深究。在 _read_images
方法中有如下代码:
1 2 3 4 5 6 7 |
if (cat->classMethods || cat->protocols /* || cat->classProperties */) { addUnattachedCategoryForClass(cat, cls->ISA(), hi); if (cls->ISA()->isRealized()) { remethodizeClass(cls->ISA()); } } |
根据注释可见苹果曾经计划利用 category 来添加属性。在 addUnattachedCategoryForClass
方法中会找到当前类的所有 category,然后在 remethodizeClass
真正的去做处理。不过到目前为止还没有接触到相关的 category 处理,我们继续沿着调用栈向下走:
1 2 3 |
void _read_images(header_info **hList, uint32_t hCount) └──static void remethodizeClass(Class cls) └──static void attachCategories(Class cls, category_list *cats, bool flush_caches) |
这里的 attachCategories
就是处理 category 的核心所在,不过在阅读这段代码之前,我们有必要先熟悉一下相关的数据结构。
Category 相关的数据结构
首先来了解一下一个 Category 是如何存储的,在 objc-runtime-new.h 中可以看到如下定义,我只列出了其中成员变量:
1 2 3 4 5 6 7 8 |
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; }; |
可见一个 category 持有了一个 method_list_t
类型的数组,method_list_t
又继承自 entsize_list_tt
,这是一种泛型容器:
可以看到其中保存了类的实例变量,方法列表等信息。
不知道有多少读者思考过 OBJC2_UNAVAILABLE
意味着什么。其实早在 2006 年,苹果在 WWDC 大会上就发布了 Objective-C 2.0,其中的改动包括 Max OS X 平台上的垃圾回收机制(现已废弃),runtime 性能优化等。
这意味着上述代码,以及任何带有 OBJC2_UNAVAILABLE
标记的内容,都已经在 2006 年就永远的告别了我们,只停留在历史的文档中。
Category 的原理
虽然上述代码已经过时,但仍具备一定的参考意义,比如 methodLists
作为一个二级指针,其中每个元素都是一个数组,数组中的每个元素则是一个方法。
接下来就介绍一下 category 的工作原理,在美团的技术博客 深入理解Objective-C:Category 中已经有了非常详细的解释,然而可能由于时间问题,其中的不少内容已经过时,我根据目前最新的版本(objc-680) 做一些简单的分析,为了便于阅读,在不影响代码逻辑的前提下有可能删除部分无关紧要的内容。
概述
首先 runtime 依赖于 dyld 动态加载,在 objc-os.mm 文件中可以找到入口,它的调用栈简单整理如下:
1 2 3 4 |
void _objc_init(void) └──const char *map_2_images(...) └──const char *map_images_nolock(...) └──void _read_images(header_info **hList, uint32_t hCount) |
以上四个方法可以理解为 runtime 的初始化过程,我们暂且不深究。在 _read_images
方法中有如下代码:
1 2 3 4 5 6 7 |
if (cat->classMethods || cat->protocols /* || cat->classProperties */) { addUnattachedCategoryForClass(cat, cls->ISA(), hi); if (cls->ISA()->isRealized()) { remethodizeClass(cls->ISA()); } } |
根据注释可见苹果曾经计划利用 category 来添加属性。在 addUnattachedCategoryForClass
方法中会找到当前类的所有 category,然后在 remethodizeClass
真正的去做处理。不过到目前为止还没有接触到相关的 category 处理,我们继续沿着调用栈向下走:
1 2 3 |
void _read_images(header_info **hList, uint32_t hCount) └──static void remethodizeClass(Class cls) └──static void attachCategories(Class cls, category_list *cats, bool flush_caches) |
这里的 attachCategories
就是处理 category 的核心所在,不过在阅读这段代码之前,我们有必要先熟悉一下相关的数据结构。
Category 相关的数据结构
首先来了解一下一个 Category 是如何存储的,在 objc-runtime-new.h 中可以看到如下定义,我只列出了其中成员变量:
1 2 3 4 5 6 7 8 |
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; }; |
可见一个 category 持有了一个 method_list_t
类型的数组,method_list_t
又继承自 entsize_list_tt
,这是一种泛型容器: