深入理解Objective-C:Category

思考题

开篇先看几道思路题,带着问题阅读会更能激起大脑的思考。当然,如果你看完思考题后,心里已经有答案了,恭喜你已经深入理解了((* ̄︶ ̄))。

  • 为什么分类中不能添加实例变量?
  • 分类中有一个和类同名的方法,调用方法名时,找的是谁的方法,为什么?
  • 类及其分类中load方法的执行顺序?
  • 关联对象是怎么保存和销毁的?

简介

分类的主要作用的是为已经存在的类添加方法。此外,在实际开发中,有如下应用场景:

  • 将类的实现拆分到多个文件中;
  • 通过关联对象实现属性的添加;
  • 把framework中的私有方法公开;
  • 模拟多继承;

和他类似的概念是extension。两者最大的区别是extension可以添加实例变量,而Category不能。extension是编译期决议的(decision),category是运行期决议的。详见官方文档

从内存管理的角度上看,在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏现有的布局。

从源码的角度上看呢,

typedef 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_t;
复制代码

从分类的定义上可以清楚的知道分类可以干什么,它可以添加实例方法、类方法、协议及属性。

运行期加载过程

分类被附加到类上是在map_images的时候发生的。首先所有分类的方法列表会被拼成一个总的新方法列表(newLists);再将类中原有的方法列表复制到新方法列表中;部分代码片段如下:

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;
            }
        }

        // 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];
    }

复制代码

到这里我们可以知道,所谓的方法覆盖,原有的方法并没有消失,只是他在方法列表中的索引后于分类中的方法。而运行时方法查找是顺着方法列表顺序查找的。所以如果要调原方法也很简单,顺着方法列表找到最后一个对应名字的方法就是原方法。

load方法

load方法的调用顺序有两条规则:父类先于子类调用,类先于分类调用。

那么,在多个分类的情况下呢?取决于编译器中的compile sources中的顺序。而后编译的,在"同名覆盖"的情况下,方法会先被找到。

关联对象

在分类中,编译器无法自动帮我们把属性所需的实例变量合成出来。在实际操作中,我们可以通过向属性的存取方法提供管理对象来达到同样的目的。

前文已经分析过,在运行期,类的内存空间都已经确定。关联对象势必另外开辟了内存,那么这个内存在哪,又是如何与类关联的呢?

class AssociationsManager {
    static OSSpinLock _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { OSSpinLockLock(&_lock); }
    ~AssociationsManager()  { OSSpinLockUnlock(&_lock); }

    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};
复制代码

所有的关联对象都有AssociationsManager管理,里面通过一个全局map来保存。map的key是这个对象的指针地址,value是另一个map,保存了关联对象的键值对。

void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa_gen = _object_getClass(obj);
        class_t *isa = newcls(isa_gen);

        // Read all of the flags at once for performance.
        bool cxx = hasCxxStructors(isa);
        bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);

        if (!UseGC) objc_clear_deallocating(obj);
    }

    return obj;
}
复制代码

当对象销毁时,objc_destructInstance会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations清理管理对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值