Objective-C runtime机制(4)——深入理解Category

本文详细探讨了Objective-C Category的实现机制,包括其数据结构、加载过程、如何影响方法调用、Category与+load方法的关系以及关联对象的使用。通过分析,揭示了Category如何在运行时动态扩展类的功能,以及如何在类方法列表中插入方法,从而实现方法的'覆盖'。
摘要由CSDN通过智能技术生成

在平日编程中或阅读第三方代码时,category可以说是无处不在。category也可以说是OC作为一门动态语言的一大特色。category为我们动态扩展类的功能提供了可能,或者我们也可以把一个庞大的类进行功能分解,按照category进行组织。

关于category的使用无需多言,今天我们来深入了解一下,category是如何在runtime中实现的。

category的数据结构

category对应到runtime中的结构体是struct 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);
};

category_t的定义很简单。从定义中看出,category 的可为:添加实例方法(instanceMethods),类方法(classMethods),协议(protocols)和实例属性(instanceProperties),以及不可为:不能够添加实例变量(关于实例属性和实例变量的区别,我们将会在别的章节中探讨)。

category的加载

知道了category的数据结构,我们来深入探究一下category是如何在runtime中实现的。

原理很简单:runtime会分别将category 结构体中的instanceMethods, protocolsinstanceProperties添加到target class的实例方法列表,协议列表,属性列表中,会将category结构体中的classMethods添加到target class所对应的元类的实例方法列表中。其本质就相当于runtime在运行时期,修改了target class的结构。

经过这一番修改,category中的方法,就变成了target class方法列表中的一部分,其调用方式也就一模一样啦~

现在,就来看一下具体是怎么实现的。

首先,我们在Mach-O格式和runtime 介绍过在Mach-O文件中,category数据会被存放在__DATA段下的__objc_catlist section中。

当OC被dyld加载起来时,OC进入其入口点函数_objc_init

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_objc_notify_register(&map_images, load_images, unmap_image);
}

我们忽略一堆init方法,重点来看_dyld_objc_notify_register方法。该方法会向dyld注册监听Mach-O中OC相关section被加载入\载出内存的事件。

具体有三个事件:
_dyld_objc_notify_mapped(对应&map_images回调):当dyld已将images加载入内存时。
_dyld_objc_notify_init(对应load_images回调):当dyld初始化image后。OC调用类的+load方法,就是在这时进行的。
_dyld_objc_notify_unmapped(对应unmap_image回调):当dyld将images移除内存时。

而category写入target class的方法列表,则是在_dyld_objc_notify_mapped,即将Mach-O相关sections都加载到内存之后所发生的。

我们可以看到其对应回调为map_images方法。

map_images 最终会调用_read_images 方法来读取OC相关sections,并以此来初始化OC内存环境。_read_images 的极简实现版如下,可以看到,rumtime是如何根据Mach-O各个section的信息来初始化其自身的:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

    static bool doneOnce;
    TimeLogger ts(PrintImageTimes);
    
    runtimeLock.assertWriting();
    
    if (!doneOnce) {
        doneOnce = YES;
        
        ts.log("IMAGE TIMES: first time tasks");
    }
    
    
    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    
    for (EACH_HEADER) {
        
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
        }
    }
    
    ts.log("IMAGE TIMES: discover classes");
    
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.

    for (EACH_HEADER) {
        Class *classrefs = _getObjc2ClassRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i]);
        }
        // fixme why doesn't test future1 catch the absence of this?
        classrefs = _getObjc2SuperRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i]);
        }
    }
   
    ts.log("IMAGE TIMES: remap classes");
    
    
    for (EACH_HEADER) {
        if (hi->isPreoptimized()) continue;
        
        bool isBundle = hi->isBundle();
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
            const char *name = sel_cname(sels[i]);
            sels[i] = sel_registerNameNoLock(name, isBundle);
        }
    }
    
    ts.log("IMAGE TIMES: fix up selector references");
    
    
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();
        
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map,
                         isPreoptimized, isBundle);
        }
    }
    
    ts.log("IMAGE TIMES: d
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值