美文网首页Fuck iOS EveryDayiOS开发之底层
解读objc源码:重新认识类和对象

解读objc源码:重新认识类和对象

作者: ElaineYin | 来源:发表于2018-06-07 17:29 被阅读38次
    1. 为什么说类也是对象?
    2. 为什么不能在运行时添加成员变量?
    3. 为什么可以动态添加方法?

    个人觉得,带着问题看答案,应该会理解更深一些,OK,开讲啦!!!

    1、认识真正的类:objc_class

    1.1 我们眼中的Class

    苹果开放给我们的objc库中对类和对象是这么定义的:

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    
    /// 对象:类的实例
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    /// 类结构体
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    从上面的代码我们看到类实例(对象)中只有一个元素,isa指针,这个指针指向的是实例所述的类,类结构体中也有一个isa指针,其他的我好像是看不出来了,现在我们从objc源代码看一下类真正的结构

    1.2 源码中的类定义

    objc_class
    代码摘自objc4-723版本,代码太长,所以只看下属性

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
        class_rw_t *data() { 
            return bits.data();
        }
    }
    

    跟我们之前看到的类定义对比一下:

    • isa指针注释掉了,先不管为什么
    • superclass:父类指针,没问题
    • cache:缓存,应该是我们知道的缓存方法的cache吧
    • bits:应该是类存储的字节吧
    • *data():bits.data()

    只有这几个属性,我们的方法列表、属性列表、变量什么的哪去了?我觉得应该跟data()有关,一看bits就是存数据的,data()是class_rw_t类型的,应该能从class_rw_t定义里面找到原因,来看下class_rw_t:


    class_rw_t

    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint32_t version;
    
        const class_ro_t *ro;
    
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        char *demangledName;
    
    #if SUPPORT_INDEXED_ISA
        uint32_t index;
    #endif
    }
    

    看到了我们熟悉的方法列表、属性列表、协议列表,又看到了一个跟class_rw_t一样的东东class_ro_t,继续深入:


    class_ro_t

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    };
    

    终于看到了我们正在寻找的成员变量列表,再看看,里面竟然还有baseMethodList、baseProtocols、baseProperties这些列表,看到这我忽然有个猜测:

    • class_rw_t 、class_ro_t中的rwro应该是readwrite、readonly的意思
    • class_ro_t存储的应该是经过编译之后的类实例的数据,保存了经过编译的变量、属性、实例方法、类方法等,编译之后这些都是readonly类型的,不能再发生改变了,那几个list就是类本来的方法属性协议啥的
    • class_rw_t是readwrite类型的,结合咱们之前了解的runtime的特性,还有里面的几个列表,顿时感觉自己已经猜到了真谛,这个是运行时的真正的类实例的结构,里面的ro是编译之后的类的结构,所以是const类型的,外面这几个列表应该是跟category有关,这么一想好像都通了。。。

    光有猜测也行不通,怎么验证一下呢,想到这里(其实是想了好久),我忽然有了个办法,我们可以用Runtime的几个copy列表的方法看一下呀
    class_copyIvarList:获取变量列表,如果我们猜测没有错误的话,返回的应该是类的class_rw_t里面的class_ro_t下面的ivarlist
    class_copyMethodList:我们猜测因为runtime可以动态添加方法、协议,那么返回的应该是class_rw_t下面的methods,而不是class_ro_t里面的,因为ro是const类型的。
    OK,来看下在objc源码中它的实现(看下关键代码就行):

    Ivar *
    class_copyIvarList(Class cls, unsigned int *outCount)
    {
        ......
    
        if ((ivars = cls->data()->ro->ivars)  &&  ivars->count) {
            result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar));
            
            for (auto& ivar : *ivars) {
                if (!ivar.offset) continue;  // anonymous bitfield
                result[count++] = &ivar;
            }
            result[count] = nil;
        }
        
        if (outCount) *outCount = count;
        return result;
    }
    
    Method *
    class_copyMethodList(Class cls, unsigned int *outCount)
    {
        ......
        count = cls->data()->methods.count();
    
        if (count > 0) {
            result = (Method *)malloc((count + 1) * sizeof(Method));
            
            count = 0;
            for (auto& meth : cls->data()->methods) {
                result[count++] = &meth;
            }
            result[count] = nil;
        }
    
        if (outCount) *outCount = count;
        return result;
    }
    

    没错,跟猜测的一样一样的,其他代码就不贴了哈,有兴趣的同学自己看看。

    这样一看,很多我们之前不理解的都能说通了:

    • 类实例是对象,它的isa指针指向类,类也有个isa指针,指向它的元类,类是元类的对象,看定义,objc_class也是objc_object没错,objc_object里面有isa指针,所以objc_class也有isa指针啦。
    • category只能添加方法、不能添加变量的原因也找到了,因为变量是存储在ro里面的,只读,方法列表协议列表在rw中,所以可以动态添加,它们俩存储位置不一样。

    感觉终于理解啦,以后别人再问我为什么不能添加变量的时候,我就能妥妥的回答啦~~ 心情好愉悦 ~~

    相关文章

      网友评论

        本文标题:解读objc源码:重新认识类和对象

        本文链接:https://www.haomeiwen.com/subject/mhlgsftx.html