objective-c中category的底层实现

以下是在看完源码后我觉得最能说明问题的部分。

首先是category结构体的定义:

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的定义也可以看出category可以添加实例方法,类方法;可以遵守协议,添加属性;但无法添加实例变量。注意,在category中可以有属性(property),但是该属性只是生成了gettersetter方法的声明,并没有产生对应的实现,更不会添加对应的实例变量。如果想为实例对象添加实例变量,可以尝试使用关联引用技术。

下面的方法初步整合了上述可以增加的选项。

static void remethodizeClass(Class cls)
{
    category_list *cats;//分类列表
    BOOL isMeta;//元类标识
    //加锁
    rwlock_assert_writing(&runtimeLock);
    
    isMeta = cls->isMetaClass();//获得元类的标识
    
    // Re-methodizing: check for more categories
    //如果该类有尚未添加的分类
    if ((cats = unattachedCategoriesForClass(cls))) {
        chained_property_list *newproperties;//指向属性的指针
        const protocol_list_t **newprotos;//指向协议的二级指针
        //输出相关信息
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s",
                         cls->name(), isMeta ? "(meta)" : "");
        }
        
        // Update methods, properties, protocols
        attachCategoryMethods(cls, cats, YES);//函数的添加相对复杂,在下面说明
        //非元类时,返回分类中的属性列表
        newproperties = buildPropertyList(nil, cats, isMeta);
        if (newproperties) {
            //将原来类中的属性列表添加在分类属性列表之后
            newproperties->next = cls->data()->properties;
            cls->data()->properties = newproperties;//更新属性列表
        }
        //根据当前类和分类中的信息构新的建协议列表
        newprotos = buildProtocolList(cats, nil, cls->data()->protocols);
        //若原本协议列表不为空,且发生了更改
        if (cls->data()->protocols  &&  cls->data()->protocols != newprotos) {
            _free_internal(cls->data()->protocols);//释放存放原来protocol的内存
        }
        cls->data()->protocols = newprotos;//重新设置协议列表
        
        _free_internal(cats);//释放分类占用的内存
    }
}

最终处理函数的方法是:

static void attachMethodLists(Class cls, method_list_t **addedLists, intaddedCount, bool baseMethods, bool methodsFromBundle, bool flushCaches)

该函数较长,主要操作如下:

method_list_t *oldBuf[2];
method_list_t **oldLists;
int oldCount = 0;
if (cls->data()->flags & RW_METHOD_ARRAY) {
    oldLists = cls->data()->method_lists;
} else {
    oldBuf[0] = cls->data()->method_list;
    oldBuf[1] = nil;
    oldLists = oldBuf;
}
if (oldLists) {//原本有方法列表的情况下计算方法列表的个数
    //每一个oldLists[i]都是一个方法列表
    while (oldLists[oldCount]) oldCount++;
}

int newCount = oldCount;
for (int i = 0; i < addedCount; i++) {
    if (addedLists[i]) newCount++;  // only non-nil entries get added
}//计算完成后newCount中的值是添加上category中的方法列表后的方法列表总值。

method_list_t *newBuf[2];
method_list_t **newLists;
if (newCount > 1) {//方法列表不只一个
    newLists = (method_list_t **)
    _malloc_internal((1 + newCount) * sizeof(*newLists));//多一个空间用于表示方法列表的结束
} else {//只有一个方法列表时
    newLists = newBuf;
}//重新分配方法列表数组的内存

// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.

newCount = 0;
int i;
for (i = 0; i < addedCount; i++) {
    method_list_t *mlist = addedLists[i];
    if (!mlist) continue;//如果mlist为空,继续搜索下一个
    
    // Fixup selectors if necessary
    if (!isMethodListFixedUp(mlist)) {
        mlist = fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
    }
    
    // Scan for method implementations tracked by the class's flags
    //根据类中的标志来寻找方法的实现
    for (uint32_t m = 0;
         (scanForCustomRR || scanForCustomAWZ)  &&  m < mlist->count;
         m++)
    {
        SEL sel = method_list_nth(mlist, m)->name;
        if (scanForCustomRR  &&  isRRSelector(sel)) {
            cls->setHasCustomRR();
            scanForCustomRR = false;
        } else if (scanForCustomAWZ  &&  isAWZSelector(sel)) {
            cls->setHasCustomAWZ();
            scanForCustomAWZ = false;
        }
    }
    
    // Update method caches
    //有需要的话,更新方法的缓存
    if (flushCaches) {
        cache_eraseMethods(cls, mlist);
    }
    
    // Fill method list array
    //逐个将新添加的方法列表加入新建的二级列表中
    newLists[newCount++] = mlist;
}

// Copy old methods to the method list array
//将原本有的方法列表添加在后面
for (i = 0; i < oldCount; i++) {
    newLists[newCount++] = oldLists[i];
}
//原本有函数列表,则释放相应的内存
if (oldLists  &&  oldLists != oldBuf) free(oldLists);

// nil-terminate
newLists[newCount] = nil;

由上面的代码可知,方法列表的添加过程如下图所示(假设添加时都只有一组方法列表):

添加方法列表的时候是后添加的在新形成的列表前部,这也是为什么在有多个category中有同名方法时,后编译的在调用时会“覆盖”前面已编译的方法。其实方法本身并没有被覆盖,只是调用的时候是从上而下查找方法列表,当运行时找到对应的方法名后就去忙着调用了,并不会管后面的同名方法。

关于load方法:

其实在上面的源码中也可以看到, category 中对load方法的处理过并没有什么特殊。因此,可以说category 中的 load 方法跟普通方法一样也会对主类中的 load方法造成覆盖,只不过 runtime在自动调用主类和 category中的 load方法时是直接使用各自方法的指针进行调用的。所以我们感觉不到category对主类的影响。其实手动给主类发送load 消息时,调用的将会是分类中的load 方法。




你可能感兴趣的:(objective-c中category的底层实现)