上一篇文章中,我们了解到,在编译阶段,每一个Category都是一个独立的结构体,其中包含实例方法、类方法、属性和遵循的协议。具体内容可参看
Category的本质(一)编译阶段都做了什么?
今天,我们来看一下在运行时,是如何将方法、属性和协议绑定到原有的类中,供我们调用。苹果官方给我们提供了objc源码,可以通过这个链接进行下载:ojbc源码
注意标号最大的是最新版本,写这篇文章时,最新的编号为756.2,也就是最新版本,大家可以下载阅读。
打开源码后,整个项目其实是不能直接运行的,因为牵涉到很多依赖库,配置起来可能需要个把小时左右,但是这并不影响我们阅读Category在运行时所做的事情。
- 首先我们通过runtime源码,来验证一下我们在上篇文章,通过clang编译后看到的Category的结构体,是否和runtime的实现是一致的。
由于我们了解到Category的实现是一个category_t结构体,在项目中搜索 Category_t关键字,发现在objc-runtime-new.h中,有如下实现:
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; /// 实例属性列表
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties; /// 类属性列表
method_list_t *methodsForMeta(bool isMeta) { ///获取实例方法列表或类方法列表
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); ///获取实例属性列表和类属性列表。
};
可以看到结构体中的成员和我们通过clang编译器得到的结构体是完全一致的,只是多了两个方法,一个是获取方法列表,一个是获取属性列表。通过对比,以后有什么疑问的话,就可以放心大胆地通过clang来编译OC代码,查看对应的C++代码,剖析本质。
记得我们的命令哦:
xcrun -sdk -iphoneos clang -arch arm64 -rewrite-objc xxx.m -o ---.cpp
- 下面我们沿着runtime的执行顺序,解读一下Category的实现。
- 在Source目录下,找到objc-os.mm文件,这是入口文件,对应的objc_init方法就是引导初始化,向dyld(动态链接库)注册镜像文件。
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
///向dyld注册镜像文件
_dyld_objc_notify_register(&map_2_images, load_images, unmap_image);
}
然后查看注册方法,如果是初次阅读runtime源码,可能会有很多疑问,为了减少不必要的疑虑,我把查找顺序梳理了一下,大家可以按照这个顺序,直接找到Category的执行方法:
关键代码:
// Discover categories.
for (EACH_HEADER) {
///二维数组
///【【instance_method_list,class_method_list,property_list,protocol_list】
/// 【instance_method_list,class_method_list,property_list,protocol_list】
/// 【instance_method_list,class_method_list,property_list,protocol_list】
/// 【instance_method_list,class_method_list,property_list,protocol_list】
/// 【instance_method_list,class_method_list,property_list,protocol_list】】
///由于一个类可以有多个Category,
///每个Category中包含属性列表、实例方法列表、类方法列表和协议列表。
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
///判断是否由弱引用导致的类已经被释放了
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
///向原有类注册Category方法、属性、协议
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
//绑定实例方法
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
代码中的关键部分,已经逐行加了注释,大家可以慢慢阅读。
到了这里,就开始对实例方法和类方法进行分别处理。类方法附加在元类对象(MetaClass)中,实例方法附加在类对象(Class)中。
我们下面以将实例方法添加到类对象为例,进行讲解。属性、协议可以类比。
关键代码在remethodizeClass中,将Category中的方法、属性、协议附加到原来的类中,同时更新缓存列表。
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
继续往下看,,我们发现attachCategories()方法,将Category附加到cls中,按照编译顺序,最后参与编译的方法将会放在最前面。同时,在将方法添加到类中以后,更新方法缓存列表。
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
///方法列表
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
///属性列表
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
///协议列表
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
///从最后一个元素开始遍历,如果多个Category中含有同名方法时,
///最终导致的结果就是,最后参与编译的方法优先执行
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
///更新方法缓存列表
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
其中还有一个比较重要的方法 attachLists(),值得我们关注,在这里实现了内存扩容,方法列表的移动和赋值功能。
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
//扩容
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//移动 memmove(dest, source, count)
//将array-list()即第一个元素开始的oldCount个元素移动到array->list+addedCount的位置
//即将队首的元素移动到队尾,开头空出addedCount个元素
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//赋值
//把addedList移动到队首,占据addedCount个位置
//即Category中的方法/属性/协议添加到列表开始的位置
//这就是我们使用Category时,如果添加和原有方法同名方法时,会优先执行我们通过Category添加的方法的原因了。
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
关键部分都有注释,希望大家可以看得明白。